Refactoring: overhaul of the visual style with CSS
|  | @ -14,6 +14,7 @@ import usersettings from "../../assets/generated/layers/usersettings.json" | ||||||
| import Locale from "../../UI/i18n/Locale" | import Locale from "../../UI/i18n/Locale" | ||||||
| import LinkToWeblate from "../../UI/Base/LinkToWeblate" | import LinkToWeblate from "../../UI/Base/LinkToWeblate" | ||||||
| import FeatureSwitchState from "./FeatureSwitchState" | import FeatureSwitchState from "./FeatureSwitchState" | ||||||
|  | import Constants from "../../Models/Constants"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, |  * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, | ||||||
|  | @ -32,6 +33,9 @@ export default class UserRelatedState { | ||||||
|     public readonly installedUserThemes: Store<string[]> |     public readonly installedUserThemes: Store<string[]> | ||||||
| 
 | 
 | ||||||
|     public readonly showAllQuestionsAtOnce: UIEventSource<boolean> |     public readonly showAllQuestionsAtOnce: UIEventSource<boolean> | ||||||
|  |     public readonly showTags:  UIEventSource<"no" | undefined | "always" | "yes">; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     public readonly homeLocation: FeatureSource |     public readonly homeLocation: FeatureSource | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -88,6 +92,7 @@ export default class UserRelatedState { | ||||||
|                     "Either 'true' or 'false'. If set, all questions will be shown all at once", |                     "Either 'true' or 'false'. If set, all questions will be shown all at once", | ||||||
|             }) |             }) | ||||||
|         ) |         ) | ||||||
|  |         this.showTags = <UIEventSource<any>> this.osmConnection.GetPreference("show_tags") | ||||||
| 
 | 
 | ||||||
|         this.mangroveIdentity = new MangroveIdentity( |         this.mangroveIdentity = new MangroveIdentity( | ||||||
|             this.osmConnection.GetLongPreference("identity", "mangrove") |             this.osmConnection.GetLongPreference("identity", "mangrove") | ||||||
|  | @ -254,6 +259,10 @@ export default class UserRelatedState { | ||||||
|             _supports_sharing: window.navigator.share ? "yes" : "no" |             _supports_sharing: window.navigator.share ? "yes" : "no" | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  |         for (const key in Constants.userJourney) { | ||||||
|  |             amendedPrefs.data["__userjourney_"+key] = Constants.userJourney[key] | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         const osmConnection = this.osmConnection |         const osmConnection = this.osmConnection | ||||||
|         osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { |         osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { | ||||||
|             for (const k in newPrefs) { |             for (const k in newPrefs) { | ||||||
|  |  | ||||||
|  | @ -637,6 +637,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> { | ||||||
|                     or: [ |                     or: [ | ||||||
|                         "__featureSwitchIsTesting=true", |                         "__featureSwitchIsTesting=true", | ||||||
|                         "__featureSwitchIsDebugging=true", |                         "__featureSwitchIsDebugging=true", | ||||||
|  |                         "mapcomplete-show_tags=full", | ||||||
|                         "mapcomplete-show_debug=yes", |                         "mapcomplete-show_debug=yes", | ||||||
|                     ], |                     ], | ||||||
|                 }, |                 }, | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ | ||||||
| 
 | 
 | ||||||
| <div class="pl-2 p-1 flex"> | <div class="pl-2 p-1 flex"> | ||||||
|   <div class="animate-spin self-center w-6 h-6 min-w-6"> |   <div class="animate-spin self-center w-6 h-6 min-w-6"> | ||||||
|   <ToSvelte construct={Svg.loading_ui}></ToSvelte> |   <ToSvelte construct={Svg.loading_svg()}></ToSvelte> | ||||||
|   </div> |   </div> | ||||||
|   <div class="ml-2"> |   <div class="ml-2"> | ||||||
|   <slot></slot> |   <slot></slot> | ||||||
|  |  | ||||||
|  | @ -8,6 +8,6 @@ | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <div on:click={e => dispatch("click", e)} class="subtle-background rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1 cursor-pointer"> | <button on:click={e => dispatch("click", e)} class="secondary rounded-full h-fit w-fit m-0.5 md:m-1 p-0.5 sm:p-1"> | ||||||
|   <slot/> |   <slot/> | ||||||
| </div> | </button> | ||||||
|  |  | ||||||
|  | @ -1,73 +1,35 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import { createEventDispatcher, onMount } from "svelte"; |   import {createEventDispatcher} from "svelte"; | ||||||
|   import { Store } from "../../Logic/UIEventSource"; |  | ||||||
|   import BaseUIElement from "../BaseUIElement"; |   import BaseUIElement from "../BaseUIElement"; | ||||||
|   import Img from "./Img"; |   import Img from "./Img"; | ||||||
|   import Translations from "../i18n/Translations"; |  | ||||||
|   import { ImmutableStore } from "../../Logic/UIEventSource.js"; |  | ||||||
| 
 | 
 | ||||||
|   export let imageUrl: string | BaseUIElement = undefined |   export let imageUrl: string | BaseUIElement = undefined | ||||||
|   export let message: string | BaseUIElement = undefined |   export let message: string | BaseUIElement = undefined | ||||||
|   export let options: { |   export let options: { | ||||||
|     url?: string | Store<string> |  | ||||||
|     newTab?: boolean |  | ||||||
|     imgSize?: string |     imgSize?: string | ||||||
|     extraClasses?: string |     extraClasses?: string | ||||||
|   } = {} |   } = {} | ||||||
| 
 | 
 | ||||||
|   // Website to open when clicked |  | ||||||
|   let href: Store<string> = undefined |  | ||||||
|   if (options?.url) { |  | ||||||
|     href = typeof options?.url == "string" ? new ImmutableStore(options.url) : options.url |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   let imgElem: HTMLElement; |  | ||||||
|   let msgElem: HTMLElement; |  | ||||||
|   let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11"); |   let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11"); | ||||||
|   const dispatch = createEventDispatcher<{click}>() |   const dispatch = createEventDispatcher<{click}>() | ||||||
| 
 |   console.log("Slots:", $$slots) | ||||||
|   onMount(() => { |  | ||||||
|     // Image |  | ||||||
|     if (imgElem && imageUrl) { |  | ||||||
|       let img: BaseUIElement |  | ||||||
| 
 |  | ||||||
|       if ((imageUrl ?? "") === "") { |  | ||||||
|         img = undefined |  | ||||||
|       } else if (typeof imageUrl !== "string") { |  | ||||||
|         img = imageUrl?.SetClass(imgClasses) |  | ||||||
|       } |  | ||||||
|       if (img) imgElem.replaceWith(img.ConstructElement()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Message |  | ||||||
|     if (msgElem && message) { |  | ||||||
|       let msg = Translations.W(message)?.SetClass("block text-ellipsis no-images flex-shrink") |  | ||||||
|       msgElem.replaceWith(msg.ConstructElement()) |  | ||||||
|     } |  | ||||||
|   }) |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <svelte:element | <button | ||||||
|   class={(options.extraClasses??"") + 'flex hover:shadow-xl transition-[color,background-color,box-shadow] hover:bg-unsubtle cursor-pointer'} |   class={(options.extraClasses??"") + 'flex hover:shadow-xl transition-[color,background-color,box-shadow] hover:bg-unsubtle cursor-pointer'} | ||||||
|   href={$href} |  | ||||||
|   target={options?.newTab ? "_blank" : ""} |   target={options?.newTab ? "_blank" : ""} | ||||||
|   this={href === undefined ? "span" : "a"} |  | ||||||
|   on:click={(e) => dispatch("click", e)} |   on:click={(e) => dispatch("click", e)} | ||||||
| > | > | ||||||
|   <slot name="image"> |   <slot name="image"> | ||||||
|     {#if imageUrl !== undefined} |     {#if imageUrl !== undefined} | ||||||
|       {#if typeof imageUrl === "string"} |       {#if typeof imageUrl === "string"} | ||||||
|         <Img src={imageUrl} class={imgClasses}></Img> |         <Img src={imageUrl} class={imgClasses}></Img> | ||||||
|       {:else } |  | ||||||
|         <template bind:this={imgElem} /> |  | ||||||
|       {/if} |       {/if} | ||||||
|     {/if} |     {/if} | ||||||
|   </slot> |   </slot> | ||||||
| 
 | 
 | ||||||
|   <slot name="message"> |   <slot name="message"/> | ||||||
|     <template bind:this={msgElem} /> | </button> | ||||||
|   </slot> |  | ||||||
| </svelte:element> |  | ||||||
| 
 | 
 | ||||||
| <style lang="scss"> | <style lang="scss"> | ||||||
|   span, |   span, | ||||||
|  |  | ||||||
|  | @ -6,6 +6,10 @@ import Lazy from "./Lazy" | ||||||
| import Loading from "./Loading" | import Loading from "./Loading" | ||||||
| import SubtleButtonSvelte from "./SubtleButton.svelte" | import SubtleButtonSvelte from "./SubtleButton.svelte" | ||||||
| import SvelteUIElement from "./SvelteUIElement" | import SvelteUIElement from "./SvelteUIElement" | ||||||
|  | import SubtleLink from "./SubtleLink.svelte"; | ||||||
|  | import Translations from "../i18n/Translations"; | ||||||
|  | import Combine from "./Combine"; | ||||||
|  | import Img from "./Img"; | ||||||
| 
 | 
 | ||||||
| export class SubtleButton extends UIElement { | export class SubtleButton extends UIElement { | ||||||
|     private readonly imageUrl: string | BaseUIElement |     private readonly imageUrl: string | BaseUIElement | ||||||
|  | @ -34,11 +38,29 @@ export class SubtleButton extends UIElement { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected InnerRender(): string | BaseUIElement { |     protected InnerRender(): string | BaseUIElement { | ||||||
|         return new SvelteUIElement(SubtleButtonSvelte, { |         if(this.options.url !== undefined){ | ||||||
|             imageUrl: this?.imageUrl ?? undefined, |             return new SvelteUIElement(SubtleLink, {href: this.options.url, newTab: this.options.newTab}) | ||||||
|             message: this?.message ?? "", |         } | ||||||
|             options: this?.options ?? {}, | 
 | ||||||
|         }) |         const classes = "block flex p-3 my-2 bg-subtle rounded-lg hover:shadow-xl hover:bg-unsubtle transition-colors transition-shadow link-no-underline"; | ||||||
|  |         const message = Translations.W(this.message)?.SetClass("block overflow-ellipsis no-images flex-shrink"); | ||||||
|  |         let img; | ||||||
|  |         const imgClasses = "block justify-center flex-none mr-4 " + (this.options?.imgSize ?? "h-11 w-11") | ||||||
|  |         if ((this.imageUrl ?? "") === "") { | ||||||
|  |             img = undefined; | ||||||
|  |         } else if (typeof (this.imageUrl) === "string") { | ||||||
|  |             img = new Img(this.imageUrl)?.SetClass(imgClasses) | ||||||
|  |         } else { | ||||||
|  |             img = this.imageUrl?.SetClass(imgClasses); | ||||||
|  |         } | ||||||
|  |         const button = new Combine([ | ||||||
|  |             img, | ||||||
|  |             message | ||||||
|  |         ]).SetClass("flex items-center group w-full") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         this.SetClass(classes) | ||||||
|  |         return button | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public OnClickWithLoading( |     public OnClickWithLoading( | ||||||
|  |  | ||||||
							
								
								
									
										63
									
								
								UI/Base/SubtleLink.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,63 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  |   import {createEventDispatcher, onMount} from "svelte"; | ||||||
|  |   import BaseUIElement from "../BaseUIElement"; | ||||||
|  |   import Img from "./Img"; | ||||||
|  | 
 | ||||||
|  |   export let imageUrl: string | BaseUIElement = undefined | ||||||
|  |   export let href: string | ||||||
|  |   export let newTab = false | ||||||
|  |   export let options: { | ||||||
|  |     imgSize?: string | ||||||
|  |     // extraClasses?: string | ||||||
|  |   } = {} | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  |   let imgElem: HTMLElement; | ||||||
|  |   let imgClasses = "block justify-center shrink-0 mr-4 " + (options?.imgSize ?? "h-11 w-11"); | ||||||
|  | 
 | ||||||
|  |   onMount(() => { | ||||||
|  |     // Image | ||||||
|  |     if (imgElem && imageUrl) { | ||||||
|  |       let img: BaseUIElement | ||||||
|  | 
 | ||||||
|  |       if ((imageUrl ?? "") === "") { | ||||||
|  |         img = undefined | ||||||
|  |       } else if (typeof imageUrl !== "string") { | ||||||
|  |         img = imageUrl?.SetClass(imgClasses) | ||||||
|  |       } | ||||||
|  |       if (img) imgElem.replaceWith(img.ConstructElement()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |   }) | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <a | ||||||
|  |   class={(options.extraClasses??"") + 'flex hover:shadow-xl transition-[color,background-color,box-shadow] hover:bg-unsubtle cursor-pointer'} | ||||||
|  |   {href} | ||||||
|  |   target={newTab ? "_blank" : ""}} | ||||||
|  | > | ||||||
|  |   <slot name="image"> | ||||||
|  |     {#if imageUrl !== undefined} | ||||||
|  |       {#if typeof imageUrl === "string"} | ||||||
|  |         <Img src={imageUrl} class={imgClasses}></Img> | ||||||
|  |       {:else } | ||||||
|  |         <template bind:this={imgElem} /> | ||||||
|  |       {/if} | ||||||
|  |     {/if} | ||||||
|  |   </slot> | ||||||
|  | 
 | ||||||
|  |   <slot/> | ||||||
|  | </a> | ||||||
|  | 
 | ||||||
|  | <style lang="scss"> | ||||||
|  |   span, | ||||||
|  |   a { | ||||||
|  |     @apply flex p-3 my-2 py-4 rounded-lg shrink-0; | ||||||
|  |     @apply items-center w-full no-underline; | ||||||
|  |     @apply bg-subtle text-black; | ||||||
|  | 
 | ||||||
|  |     :global(span) { | ||||||
|  |       @apply block text-ellipsis; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </style> | ||||||
|  | @ -19,10 +19,10 @@ | ||||||
| <div class="tabbedgroup w-full h-full"> | <div class="tabbedgroup w-full h-full"> | ||||||
|     <TabGroup class="h-full w-full flex flex-col" defaultIndex={1} |     <TabGroup class="h-full w-full flex flex-col" defaultIndex={1} | ||||||
|               on:change={(e) =>{if(e.detail >= 0){tab.setData( e.detail); }} }> |               on:change={(e) =>{if(e.detail >= 0){tab.setData( e.detail); }} }> | ||||||
|         <div class="tablist flex bg-gray-300 items-center justify-between sticky top-0"> |         <div class="interactive flex items-center justify-between sticky top-0"> | ||||||
|             <TabList class="flex flex-wrap"> |             <TabList class="flex flex-wrap"> | ||||||
|                 {#if $$slots.title1} |                 {#if $$slots.title1} | ||||||
|                     <Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}> |                     <Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}> | ||||||
|                         <div bind:this={tabElements[0]} class="flex"> |                         <div bind:this={tabElements[0]} class="flex"> | ||||||
|                             <slot name="title0"> |                             <slot name="title0"> | ||||||
|                                 Tab 0 |                                 Tab 0 | ||||||
|  | @ -31,28 +31,28 @@ | ||||||
|                     </Tab> |                     </Tab> | ||||||
|                 {/if} |                 {/if} | ||||||
|                 {#if $$slots.title1} |                 {#if $$slots.title1} | ||||||
|                     <Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}> |                     <Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}> | ||||||
|                         <div bind:this={tabElements[1]} class="flex"> |                         <div bind:this={tabElements[1]} class="flex"> | ||||||
|                             <slot name="title1"/> |                             <slot name="title1"/> | ||||||
|                         </div> |                         </div> | ||||||
|                     </Tab> |                     </Tab> | ||||||
|                 {/if} |                 {/if} | ||||||
|                 {#if $$slots.title2} |                 {#if $$slots.title2} | ||||||
|                     <Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}> |                     <Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}> | ||||||
|                         <div bind:this={tabElements[2]} class="flex"> |                         <div bind:this={tabElements[2]} class="flex"> | ||||||
|                             <slot name="title2"/> |                             <slot name="title2"/> | ||||||
|                         </div> |                         </div> | ||||||
|                     </Tab> |                     </Tab> | ||||||
|                 {/if} |                 {/if} | ||||||
|                 {#if $$slots.title3} |                 {#if $$slots.title3} | ||||||
|                     <Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}> |                     <Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}> | ||||||
|                         <div bind:this={tabElements[3]} class="flex"> |                         <div bind:this={tabElements[3]} class="flex"> | ||||||
|                             <slot name="title3"/> |                             <slot name="title3"/> | ||||||
|                         </div> |                         </div> | ||||||
|                     </Tab> |                     </Tab> | ||||||
|                 {/if} |                 {/if} | ||||||
|                 {#if $$slots.title4} |                 {#if $$slots.title4} | ||||||
|                     <Tab class={({selected}) => "tab "+(selected ? "tab-selected" : "tab-unselected")}> |                     <Tab class={({selected}) => "tab "+(selected ? "selected" : "secondary")}> | ||||||
|                         <div bind:this={tabElements[4]} class="flex"> |                         <div bind:this={tabElements[4]} class="flex"> | ||||||
|                             <slot name="title4"/> |                             <slot name="title4"/> | ||||||
|                         </div> |                         </div> | ||||||
|  | @ -110,15 +110,6 @@ | ||||||
|         display: flex; |         display: flex; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     :global(.tab-selected) { |  | ||||||
|         /** |  | ||||||
|         For some reason, the exported tailwind style takes priority in production (but not in development) |  | ||||||
|         As the tabs are buttons, tailwind restyles them |  | ||||||
|          */ |  | ||||||
|         background-color: var(--catch-detail-color) !important; |  | ||||||
|         color: var(--catch-detail-color-contrast) !important; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     :global(.tab-selected svg) { |     :global(.tab-selected svg) { | ||||||
|         fill: var(--catch-detail-color-contrast); |         fill: var(--catch-detail-color-contrast); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -3,36 +3,30 @@ | ||||||
|     import {UIEventSource} from "../../Logic/UIEventSource" |     import {UIEventSource} from "../../Logic/UIEventSource" | ||||||
|     import Constants from "../../Models/Constants" |     import Constants from "../../Models/Constants" | ||||||
|     import Svg from "../../Svg" |     import Svg from "../../Svg" | ||||||
|     import SubtleButton from "../Base/SubtleButton.svelte" |  | ||||||
|     import ToSvelte from "../Base/ToSvelte.svelte" |     import ToSvelte from "../Base/ToSvelte.svelte" | ||||||
|     import Translations from "../i18n/Translations" |     import Translations from "../i18n/Translations" | ||||||
|  |     import SubtleLink from "../Base/SubtleLink.svelte"; | ||||||
|  |     import Tr from "../Base/Tr.svelte"; | ||||||
| 
 | 
 | ||||||
|     export let userDetails: UIEventSource<UserDetails> |     export let userDetails: UIEventSource<UserDetails> | ||||||
|     const t = Translations.t.general.morescreen |     const t = Translations.t.general.morescreen | ||||||
| 
 | 
 | ||||||
|     console.log($userDetails.csCount < 50) |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div> | <div> | ||||||
|     {#if $userDetails.csCount < Constants.userJourney.themeGeneratorReadOnlyUnlock} |     {#if $userDetails.csCount < Constants.userJourney.themeGeneratorReadOnlyUnlock} | ||||||
|         <SubtleButton |         <SubtleLink | ||||||
|                 options={{ |                 url="https://github.com/pietervdvn/MapComplete/issues" | ||||||
|         url: "https://github.com/pietervdvn/MapComplete/issues", |                 newTab={true} | ||||||
|         newTab: true, |  | ||||||
|       }} |  | ||||||
|         > |         > | ||||||
|             <span slot="message">{t.requestATheme.toString()}</span> |             <Tr t={t.requestATheme}/> | ||||||
|         </SubtleButton> |         </SubtleLink> | ||||||
|     {:else} |     {:else} | ||||||
|         <SubtleButton |         <SubtleLink href="https://pietervdvn.github.io/mc/legacy/070/customGenerator.html"> | ||||||
|                 options={{ |           <span slot="image"> | ||||||
|         url: "https://pietervdvn.github.io/mc/legacy/070/customGenerator.html", |             <ToSvelte construct={Svg.pencil_svg().SetClass("h-11 w-11 mx-4 bg-red")}/> | ||||||
|       }} |           </span> | ||||||
|         > |             <Tr t={t.createYourOwnTheme}/> | ||||||
|       <span slot="image"> |         </SubtleLink> | ||||||
|         <ToSvelte construct={Svg.pencil_svg().SetClass("h-11 w-11 mx-4 bg-red")}/> |  | ||||||
|       </span> |  | ||||||
|             <span slot="message">{t.createYourOwnTheme.toString()}</span> |  | ||||||
|         </SubtleButton> |  | ||||||
|     {/if} |     {/if} | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -109,51 +109,6 @@ export default class MoreScreen extends Combine { | ||||||
|         ]) |         ]) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Creates a button linking to the given theme |  | ||||||
|      * @private |  | ||||||
|      */ |  | ||||||
|     public static createLinkButton( |  | ||||||
|         state: { |  | ||||||
|             locationControl?: UIEventSource<Loc> |  | ||||||
|             layoutToUse?: LayoutConfig |  | ||||||
|         }, |  | ||||||
|         layout: { |  | ||||||
|             id: string |  | ||||||
|             icon: string |  | ||||||
|             title: any |  | ||||||
|             shortDescription: any |  | ||||||
|             definition?: any |  | ||||||
|             mustHaveLanguage?: boolean |  | ||||||
|         }, |  | ||||||
|         isCustom: boolean = false |  | ||||||
|     ): BaseUIElement { |  | ||||||
|         const url = MoreScreen.createUrlFor(layout, isCustom, state) |  | ||||||
|         let content = new Combine([ |  | ||||||
|             new Translation( |  | ||||||
|                 layout.title, |  | ||||||
|                 !isCustom && !layout.mustHaveLanguage ? "themes:" + layout.id + ".title" : undefined |  | ||||||
|             ), |  | ||||||
|             new Translation(layout.shortDescription)?.SetClass("subtle") ?? "", |  | ||||||
|         ]).SetClass("overflow-hidden flex flex-col") |  | ||||||
| 
 |  | ||||||
|         if (state.layoutToUse === undefined) { |  | ||||||
|             // Currently on the index screen: we style the buttons equally large
 |  | ||||||
|             content = new Combine([content]).SetClass("flex flex-col justify-center h-24") |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return new SubtleButton(layout.icon, content, { url, newTab: false }) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static CreateProffessionalSerivesButton() { |  | ||||||
|         const t = Translations.t.professional.indexPage |  | ||||||
|         return new Combine([ |  | ||||||
|             new Title(t.hook, 4), |  | ||||||
|             t.hookMore, |  | ||||||
|             new SubtleButton(undefined, t.button, { url: "./professional.html" }), |  | ||||||
|         ]).SetClass("flex flex-col border border-gray-300 p-2 rounded-lg") |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static MatchesLayout( |     public static MatchesLayout( | ||||||
|         layout: { |         layout: { | ||||||
|             id: string |             id: string | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ | ||||||
|   import SubtleButton from "../Base/SubtleButton.svelte" |   import SubtleButton from "../Base/SubtleButton.svelte" | ||||||
|   import ToSvelte from "../Base/ToSvelte.svelte" |   import ToSvelte from "../Base/ToSvelte.svelte" | ||||||
|   import Translations from "../i18n/Translations" |   import Translations from "../i18n/Translations" | ||||||
|  |   import Tr from "../Base/Tr.svelte"; | ||||||
| 
 | 
 | ||||||
|   export let search: UIEventSource<string> |   export let search: UIEventSource<string> | ||||||
| 
 | 
 | ||||||
|  | @ -30,14 +31,12 @@ | ||||||
|       search.setData("") |       search.setData("") | ||||||
|     }} |     }} | ||||||
|   > |   > | ||||||
|     <span> |  | ||||||
|       <SubtleButton> |       <SubtleButton> | ||||||
|         <span slot="image"> |         <span slot="image"> | ||||||
|           <ToSvelte construct={Svg.search_disable_svg().SetClass("w-6 mr-2")} /> |           <ToSvelte construct={Svg.search_disable_svg().SetClass("w-6 mr-2")} /> | ||||||
|         </span> |         </span> | ||||||
|         <span slot="message">{t.noSearch.toString()}</span> |         <Tr t={t.noSearch} slot="message"/> | ||||||
|       </SubtleButton> |       </SubtleButton> | ||||||
|     </span> |  | ||||||
|   </button> |   </button> | ||||||
| </span> | </span> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import SubtleButton from "../Base/SubtleButton.svelte" |  | ||||||
|   import Title from "../Base/Title" |   import Title from "../Base/Title" | ||||||
|   import ToSvelte from "../Base/ToSvelte.svelte" |   import ToSvelte from "../Base/ToSvelte.svelte" | ||||||
|   import Translations from "../i18n/Translations" |   import Translations from "../i18n/Translations" | ||||||
|  |   import SubtleLink from "../Base/SubtleLink.svelte"; | ||||||
|  |   import Tr from "../Base/Tr.svelte"; | ||||||
| 
 | 
 | ||||||
|   const t = Translations.t.professional.indexPage |   const t = Translations.t.professional.indexPage | ||||||
| </script> | </script> | ||||||
|  | @ -12,9 +13,9 @@ | ||||||
|   <span> |   <span> | ||||||
|     {t.hookMore.toString()} |     {t.hookMore.toString()} | ||||||
|   </span> |   </span> | ||||||
|   <SubtleButton options={{ url: "./professional.html" }}> |   <SubtleLink href="./professional.html"> | ||||||
|     <span slot="message">{t.button.toString()}</span> |     <Tr slot="message" t={t.button} /> | ||||||
|   </SubtleButton> |   </SubtleLink> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <style lang="scss"> | <style lang="scss"> | ||||||
|  |  | ||||||
|  | @ -1,114 +1,94 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import SubtleButton from "../Base/SubtleButton.svelte" |     import {Translation} from "../i18n/Translation" | ||||||
|   import { Translation } from "../i18n/Translation" |     import * as personal from "../../assets/themes/personal/personal.json" | ||||||
|   import * as personal from "../../assets/themes/personal/personal.json" |     import {ImmutableStore, Store, UIEventSource} from "../../Logic/UIEventSource" | ||||||
|   import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" |     import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection" | ||||||
|   import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection" |     import Constants from "../../Models/Constants" | ||||||
|   import Constants from "../../Models/Constants" |     import type Loc from "../../Models/Loc" | ||||||
|   import type Loc from "../../Models/Loc" |     import type {LayoutInformation} from "../../Models/ThemeConfig/LayoutConfig" | ||||||
|   import type { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" |     import Tr from "../Base/Tr.svelte" | ||||||
|   import Tr from "../Base/Tr.svelte" |     import SubtleLink from "../Base/SubtleLink.svelte"; | ||||||
| 
 | 
 | ||||||
|   export let theme: LayoutInformation |     export let theme: LayoutInformation | ||||||
|   export let isCustom: boolean = false |     export let isCustom: boolean = false | ||||||
|   export let userDetails: UIEventSource<UserDetails> |     export let userDetails: UIEventSource<UserDetails> | ||||||
|   export let state: { osmConnection: OsmConnection; locationControl?: UIEventSource<Loc> } |     export let state: { osmConnection: OsmConnection; locationControl?: UIEventSource<Loc> } | ||||||
| 
 | 
 | ||||||
|   $: title = new Translation( |     $: title = new Translation( | ||||||
|     theme.title, |         theme.title, | ||||||
|     !isCustom && !theme.mustHaveLanguage ? "themes:" + theme.id + ".title" : undefined |         !isCustom && !theme.mustHaveLanguage ? "themes:" + theme.id + ".title" : undefined | ||||||
|   ) |  | ||||||
|   $: description = new Translation(theme.shortDescription) |  | ||||||
| 
 |  | ||||||
|   // TODO: Improve this function |  | ||||||
|   function createUrl( |  | ||||||
|     layout: { id: string; definition?: string }, |  | ||||||
|     isCustom: boolean, |  | ||||||
|     state?: { locationControl?: UIEventSource<{ lat; lon; zoom }>; layoutToUse?: { id } } |  | ||||||
|   ): Store<string> { |  | ||||||
|     if (layout === undefined) { |  | ||||||
|       return undefined |  | ||||||
|     } |  | ||||||
|     if (layout.id === undefined) { |  | ||||||
|       console.error("ID is undefined for layout", layout) |  | ||||||
|       return undefined |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (layout.id === state?.layoutToUse?.id) { |  | ||||||
|       return undefined |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const currentLocation = state?.locationControl |  | ||||||
| 
 |  | ||||||
|     let path = window.location.pathname |  | ||||||
|     // Path starts with a '/' and contains everything, e.g. '/dir/dir/page.html' |  | ||||||
|     path = path.substr(0, path.lastIndexOf("/")) |  | ||||||
|     // Path will now contain '/dir/dir', or empty string in case of nothing |  | ||||||
|     if (path === "") { |  | ||||||
|       path = "." |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?` |  | ||||||
|     if (location.hostname === "localhost" || location.hostname === "127.0.0.1" || location.port === "1234") { |  | ||||||
|       // Redirect to 'theme.html?layout=* instead of 'layout.html'. This is probably a debug run, where the routing does not work |  | ||||||
|       linkPrefix = `${path}/theme.html?layout=${layout.id}&` |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (isCustom) { |  | ||||||
|       linkPrefix = `${path}/theme.html?userlayout=${layout.id}&` |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let hash = "" |  | ||||||
|     if (layout.definition !== undefined) { |  | ||||||
|       hash = "#" + btoa(JSON.stringify(layout.definition)) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ( |  | ||||||
|       currentLocation?.map((currentLocation) => { |  | ||||||
|         const params = [ |  | ||||||
|           ["z", currentLocation?.zoom], |  | ||||||
|           ["lat", currentLocation?.lat], |  | ||||||
|           ["lon", currentLocation?.lon], |  | ||||||
|         ] |  | ||||||
|           .filter((part) => part[1] !== undefined) |  | ||||||
|           .map((part) => part[0] + "=" + part[1]) |  | ||||||
|           .join("&") |  | ||||||
|         return `${linkPrefix}${params}${hash}` |  | ||||||
|       }) ?? new ImmutableStore<string>(`${linkPrefix}`) |  | ||||||
|     ) |     ) | ||||||
|   } |     $: description = new Translation(theme.shortDescription) | ||||||
|  | 
 | ||||||
|  |     // TODO: Improve this function | ||||||
|  |     function createUrl( | ||||||
|  |         layout: { id: string; definition?: string }, | ||||||
|  |         isCustom: boolean, | ||||||
|  |         state?: { locationControl?: UIEventSource<{ lat; lon; zoom }>; layoutToUse?: { id } } | ||||||
|  |     ): Store<string> { | ||||||
|  |         if (layout === undefined) { | ||||||
|  |             return undefined | ||||||
|  |         } | ||||||
|  |         if (layout.id === undefined) { | ||||||
|  |             console.error("ID is undefined for layout", layout) | ||||||
|  |             return undefined | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (layout.id === state?.layoutToUse?.id) { | ||||||
|  |             return undefined | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const currentLocation = state?.locationControl | ||||||
|  | 
 | ||||||
|  |         let path = window.location.pathname | ||||||
|  |         // Path starts with a '/' and contains everything, e.g. '/dir/dir/page.html' | ||||||
|  |         path = path.substr(0, path.lastIndexOf("/")) | ||||||
|  |         // Path will now contain '/dir/dir', or empty string in case of nothing | ||||||
|  |         if (path === "") { | ||||||
|  |             path = "." | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?` | ||||||
|  |         if (location.hostname === "localhost" || location.hostname === "127.0.0.1" || location.port === "1234") { | ||||||
|  |             // Redirect to 'theme.html?layout=* instead of 'layout.html'. This is probably a debug run, where the routing does not work | ||||||
|  |             linkPrefix = `${path}/theme.html?layout=${layout.id}&` | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (isCustom) { | ||||||
|  |             linkPrefix = `${path}/theme.html?userlayout=${layout.id}&` | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let hash = "" | ||||||
|  |         if (layout.definition !== undefined) { | ||||||
|  |             hash = "#" + btoa(JSON.stringify(layout.definition)) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return ( | ||||||
|  |             currentLocation?.map((currentLocation) => { | ||||||
|  |                 const params = [ | ||||||
|  |                     ["z", currentLocation?.zoom], | ||||||
|  |                     ["lat", currentLocation?.lat], | ||||||
|  |                     ["lon", currentLocation?.lon], | ||||||
|  |                 ] | ||||||
|  |                     .filter((part) => part[1] !== undefined) | ||||||
|  |                     .map((part) => part[0] + "=" + part[1]) | ||||||
|  |                     .join("&") | ||||||
|  |                 return `${linkPrefix}${params}${hash}` | ||||||
|  |             }) ?? new ImmutableStore<string>(`${linkPrefix}`) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let href = createUrl(theme, isCustom, state) | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if theme.id !== personal.id || $userDetails.csCount > Constants.userJourney.personalLayoutUnlock} | {#if theme.id !== personal.id || $userDetails.csCount > Constants.userJourney.personalLayoutUnlock} | ||||||
|   <div> |     <SubtleLink href={ $href  }> | ||||||
|     <SubtleButton options={{ url: createUrl(theme, isCustom, state) }}> |         <img slot="image" src={theme.icon} class="block h-11 w-11 bg-red mx-4" alt=""/> | ||||||
|       <img slot="image" src={theme.icon} class="block h-11 w-11 bg-red mx-4" alt="" /> |         <span class="flex flex-col text-ellipsis overflow-hidden"> | ||||||
|       <span slot="message" class="message"> |           <Tr t={title}/> | ||||||
|         <span> |           <span class="subtle max-h-12"> | ||||||
|           <Tr t={title}></Tr> |             <Tr t={description}/> | ||||||
|           <span class="subtle"> |  | ||||||
|             <Tr t={description}></Tr> |  | ||||||
|           </span> |           </span> | ||||||
|         </span> |  | ||||||
|       </span> |       </span> | ||||||
|     </SubtleButton> |     </SubtleLink> | ||||||
|   </div> |  | ||||||
| {/if} | {/if} | ||||||
| 
 |  | ||||||
| <style lang="scss"> |  | ||||||
|   div { |  | ||||||
|     @apply h-32 min-h-[8rem] max-h-32 text-ellipsis overflow-hidden; |  | ||||||
| 
 |  | ||||||
|     span.message { |  | ||||||
|       @apply flex flex-col justify-center h-24; |  | ||||||
| 
 |  | ||||||
|       & > span { |  | ||||||
|         @apply flex flex-col overflow-hidden; |  | ||||||
| 
 |  | ||||||
|         span:nth-child(2) { |  | ||||||
|           @apply text-[#999]; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
|  | @ -17,7 +17,9 @@ | ||||||
|   const t = Translations.t.general |   const t = Translations.t.general | ||||||
|   const currentIds: Store<string[]> = state.installedUserThemes |   const currentIds: Store<string[]> = state.installedUserThemes | ||||||
|   const stableIds = Stores.ListStabilized<string>(currentIds) |   const stableIds = Stores.ListStabilized<string>(currentIds) | ||||||
|  |   let customThemes | ||||||
|   $: customThemes = Utils.NoNull($stableIds.map((id) => state.GetUnofficialTheme(id))) |   $: customThemes = Utils.NoNull($stableIds.map((id) => state.GetUnofficialTheme(id))) | ||||||
|  |   $: console.log("Custom themes are", customThemes) | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <ThemesList | <ThemesList | ||||||
|  |  | ||||||
|  | @ -28,7 +28,6 @@ export class CheckBox extends InputElementMap<number[], boolean> { | ||||||
|  */ |  */ | ||||||
| export default class CheckBoxes extends InputElement<number[]> { | export default class CheckBoxes extends InputElement<number[]> { | ||||||
|     private static _nextId = 0 |     private static _nextId = 0 | ||||||
|     IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false) |  | ||||||
|     private readonly value: UIEventSource<number[]> |     private readonly value: UIEventSource<number[]> | ||||||
|     private readonly _elements: BaseUIElement[] |     private readonly _elements: BaseUIElement[] | ||||||
| 
 | 
 | ||||||
|  | @ -65,12 +64,12 @@ export default class CheckBoxes extends InputElement<number[]> { | ||||||
| 
 | 
 | ||||||
|             const label = document.createElement("label") |             const label = document.createElement("label") | ||||||
|             label.htmlFor = input.id |             label.htmlFor = input.id | ||||||
|  |             label.appendChild(input) | ||||||
|             label.appendChild(inputI.ConstructElement()) |             label.appendChild(inputI.ConstructElement()) | ||||||
|             label.classList.add("block", "w-full", "p-2", "cursor-pointer", "bg-red") |             label.classList.add("block", "w-full", "p-2", "cursor-pointer", "bg-red") | ||||||
| 
 | 
 | ||||||
|             const wrapper = document.createElement("div") |             const wrapper = document.createElement("div") | ||||||
|             wrapper.classList.add("wrapper", "flex", "w-full", "border", "border-gray-400", "mb-1") |             wrapper.classList.add("wrapper", "flex", "w-full", "border", "border-gray-400", "mb-1") | ||||||
|             wrapper.appendChild(input) |  | ||||||
|             wrapper.appendChild(label) |             wrapper.appendChild(label) | ||||||
|             formTag.appendChild(wrapper) |             formTag.appendChild(wrapper) | ||||||
| 
 | 
 | ||||||
|  | @ -78,11 +77,9 @@ export default class CheckBoxes extends InputElement<number[]> { | ||||||
|                 input.checked = selectedValues.indexOf(i) >= 0 |                 input.checked = selectedValues.indexOf(i) >= 0 | ||||||
| 
 | 
 | ||||||
|                 if (input.checked) { |                 if (input.checked) { | ||||||
|                     wrapper.classList.remove("border-gray-400") |                     wrapper.classList.add("checked") | ||||||
|                     wrapper.classList.add("border-black") |  | ||||||
|                 } else { |                 } else { | ||||||
|                     wrapper.classList.add("border-gray-400") |                     wrapper.classList.remove("checked") | ||||||
|                     wrapper.classList.remove("border-black") |  | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -69,6 +69,7 @@ | ||||||
|     if (index.data === forceIndex) { |     if (index.data === forceIndex) { | ||||||
|       forceIndex = undefined; |       forceIndex = undefined; | ||||||
|     } |     } | ||||||
|  |     top = Math.max(top, 0) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   Stores.Chronic(50).addCallback(_ => stabilize()); |   Stores.Chronic(50).addCallback(_ => stabilize()); | ||||||
|  | @ -103,7 +104,7 @@ | ||||||
|   <div class="h-full absolute w-min right-0"> |   <div class="h-full absolute w-min right-0"> | ||||||
|     {#each $floors as floor, i} |     {#each $floors as floor, i} | ||||||
|       <button style={`height: ${HEIGHT}px; width: ${HEIGHT}px`} |       <button style={`height: ${HEIGHT}px; width: ${HEIGHT}px`} | ||||||
|               class={"border-2 border-gray-300 flex content-box justify-center items-center "+(i === (forceIndex ?? $index) ? "selected": "normal-background" ) |               class={"m-0 border-2 border-gray-300 flex content-box justify-center items-center "+(i === (forceIndex ?? $index) ? "selected": "" ) | ||||||
|            } |            } | ||||||
|               on:click={() => {forceIndex = i}} |               on:click={() => {forceIndex = i}} | ||||||
|       > {floor}</button> |       > {floor}</button> | ||||||
|  | @ -119,11 +120,6 @@ | ||||||
| <svelte:window on:mousemove={onMove} on:mouseup={unclick} /> | <svelte:window on:mousemove={onMove} on:mouseup={unclick} /> | ||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
|     .selected { |  | ||||||
|         background: var(--subtle-detail-color); |  | ||||||
|         font-weight: bold; |  | ||||||
|         border-color: black; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     .draggable { |     .draggable { | ||||||
|         user-select: none; |         user-select: none; | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ | ||||||
|     // The type changed -> reset some values |     // The type changed -> reset some values | ||||||
|     validator = Validators.get(type) |     validator = Validators.get(type) | ||||||
|     _value.setData(value.data ?? "") |     _value.setData(value.data ?? "") | ||||||
|  |     console.log("REseting validated input, _value is ", _value.data, validator?.getFeedback(_value.data, getCountry)) | ||||||
|     feedback =  feedback?.setData(validator?.getFeedback(_value.data, getCountry)); |     feedback =  feedback?.setData(validator?.getFeedback(_value.data, getCountry)); | ||||||
|     _placeholder = placeholder ?? validator?.getPlaceholder() ?? type |     _placeholder = placeholder ?? validator?.getPlaceholder() ?? type | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -44,8 +44,13 @@ export abstract class Validator { | ||||||
|     /** |     /** | ||||||
|      * Gets a piece of feedback. By default, validation.<type> will be used, resulting in a generic 'not a valid <type>'. |      * Gets a piece of feedback. By default, validation.<type> will be used, resulting in a generic 'not a valid <type>'. | ||||||
|      * However, inheritors might overwrite this to give more specific feedback |      * However, inheritors might overwrite this to give more specific feedback | ||||||
|  |      * | ||||||
|  |      * Returns 'undefined' if the element is valid | ||||||
|      */ |      */ | ||||||
|     public getFeedback(s: string, requestCountry?: () => string): Translation { |     public getFeedback(s: string, requestCountry?: () => string): Translation | undefined { | ||||||
|  |         if(this.isValid(s)){ | ||||||
|  |             return undefined | ||||||
|  |         } | ||||||
|         const tr = Translations.t.validation[this.name] |         const tr = Translations.t.validation[this.name] | ||||||
|         if (tr !== undefined) { |         if (tr !== undefined) { | ||||||
|             return tr["feedback"] |             return tr["feedback"] | ||||||
|  |  | ||||||
|  | @ -10,6 +10,9 @@ export default class PhoneValidator extends Validator { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     getFeedback(s: string, requestCountry?: () => string): Translation { |     getFeedback(s: string, requestCountry?: () => string): Translation { | ||||||
|  |         if(this.isValid(s, requestCountry)){ | ||||||
|  |             return undefined | ||||||
|  |         } | ||||||
|         const tr = Translations.t.validation.phone |         const tr = Translations.t.validation.phone | ||||||
|         const generic = tr.feedback |         const generic = tr.feedback | ||||||
|         if(requestCountry){ |         if(requestCountry){ | ||||||
|  |  | ||||||
|  | @ -230,7 +230,7 @@ | ||||||
|       {/each} |       {/each} | ||||||
|       </span> |       </span> | ||||||
|     {/if} |     {/if} | ||||||
|     <TagHint embedIn={tags => t.presetInfo.Subs({tags})} osmConnection={state.osmConnection} |     <TagHint embedIn={tags => t.presetInfo.Subs({tags})} {state} | ||||||
|              tags={new And(selectedPreset.preset.tags)}></TagHint> |              tags={new And(selectedPreset.preset.tags)}></TagHint> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,35 +1,41 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import { OsmConnection } from "../../Logic/Osm/OsmConnection"; |     import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||||
|   import { TagsFilter } from "../../Logic/Tags/TagsFilter"; |     import FromHtml from "../Base/FromHtml.svelte"; | ||||||
|   import FromHtml from "../Base/FromHtml.svelte"; |     import Constants from "../../Models/Constants.js"; | ||||||
|   import Constants from "../../Models/Constants.js"; |     import {Translation} from "../i18n/Translation"; | ||||||
|   import { Translation } from "../i18n/Translation"; |     import Tr from "../Base/Tr.svelte"; | ||||||
|   import Tr from "../Base/Tr.svelte"; |     import {onDestroy} from "svelte"; | ||||||
|   import { onDestroy } from "svelte"; |     import type {SpecialVisualizationState} from "../SpecialVisualization"; | ||||||
| 
 | 
 | ||||||
|   /** |     /** | ||||||
|    * A 'TagHint' will show the given tags in a human readable form. |      * A 'TagHint' will show the given tags in a human readable form. | ||||||
|    * Depending on the options, it'll link through to the wiki or might be completely hidden |      * Depending on the options, it'll link through to the wiki or might be completely hidden | ||||||
|    */ |      */ | ||||||
|   export let tags: TagsFilter; |     export let tags: TagsFilter; | ||||||
|   export let osmConnection: OsmConnection; |     export let state: SpecialVisualizationState; | ||||||
|   /** |     /** | ||||||
|    * If given, this function will be called to embed the given tags hint into this translation |      * If given, this function will be called to embed the given tags hint into this translation | ||||||
|    */ |      */ | ||||||
|   export let embedIn: (() => Translation) | undefined = undefined; |     export let embedIn: (() => Translation) | undefined = undefined; | ||||||
|   const userDetails = osmConnection.userDetails; |     const userDetails = state.osmConnection.userDetails; | ||||||
|   let linkToWiki = false; |     let linkToWiki = false; | ||||||
|   onDestroy(osmConnection.userDetails.addCallbackAndRunD(userdetails => { |     onDestroy(state.osmConnection.userDetails.addCallbackAndRunD(userdetails => { | ||||||
|     linkToWiki = userdetails.csCount > Constants.userJourney.tagsVisibleAndWikiLinked; |         linkToWiki = userdetails.csCount > Constants.userJourney.tagsVisibleAndWikiLinked; | ||||||
|   })); |     })); | ||||||
|   let tagsExplanation = ""; |     let tagsExplanation = ""; | ||||||
|   $: tagsExplanation = tags?.asHumanString(linkToWiki, false, {}); |     $: tagsExplanation = tags?.asHumanString(linkToWiki, false, {}); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if $userDetails.loggedIn} | {#if $userDetails.loggedIn} | ||||||
|   {#if embedIn === undefined} |     <div> | ||||||
|     <FromHtml src={tagsExplanation} /> |         {#if tags === undefined} | ||||||
|   {:else} |             <slot name="no-tags"> | ||||||
|     <Tr t={embedIn(tagsExplanation)} /> |                 No tags | ||||||
|   {/if} |             </slot> | ||||||
|  |         {:else if embedIn === undefined} | ||||||
|  |             <FromHtml src={tagsExplanation}/> | ||||||
|  |         {:else} | ||||||
|  |             <Tr t={embedIn(tagsExplanation)}/> | ||||||
|  |         {/if} | ||||||
|  |     </div> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -1,51 +1,48 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import { UIEventSource } from "../../../Logic/UIEventSource"; |     import {UIEventSource} from "../../../Logic/UIEventSource"; | ||||||
|   import { Translation } from "../../i18n/Translation"; |     import {Translation} from "../../i18n/Translation"; | ||||||
|   import ValidatedInput from "../../InputElement/ValidatedInput.svelte"; |     import ValidatedInput from "../../InputElement/ValidatedInput.svelte"; | ||||||
|   import Tr from "../../Base/Tr.svelte"; |     import Tr from "../../Base/Tr.svelte"; | ||||||
|   import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"; |     import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"; | ||||||
|   import Inline from "./Inline.svelte"; |     import Inline from "./Inline.svelte"; | ||||||
|   import { createEventDispatcher, onDestroy } from "svelte"; |     import {createEventDispatcher, onDestroy} from "svelte"; | ||||||
|   import InputHelper from "../../InputElement/InputHelper.svelte"; |     import InputHelper from "../../InputElement/InputHelper.svelte"; | ||||||
|   import type { Feature } from "geojson"; |     import type {Feature} from "geojson"; | ||||||
| 
 | 
 | ||||||
|   export let value: UIEventSource<string>; |     export let value: UIEventSource<string>; | ||||||
|   export let config: TagRenderingConfig; |     export let config: TagRenderingConfig; | ||||||
|   export let tags: UIEventSource<Record<string, string>>; |     export let tags: UIEventSource<Record<string, string>>; | ||||||
| 
 | 
 | ||||||
|   export let feature: Feature = undefined; |     export let feature: Feature = undefined; | ||||||
|    |  | ||||||
|   let placeholder = config.freeform?.placeholder |  | ||||||
|   $: { |  | ||||||
|     placeholder = config.freeform?.placeholder |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined); |     let placeholder = config.freeform?.placeholder | ||||||
|  |     $: { | ||||||
|  |         placeholder = config.freeform?.placeholder | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|   let dispatch = createEventDispatcher<{ "selected" }>(); |     export let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined); | ||||||
|   onDestroy(value.addCallbackD(() => {dispatch("selected")})) | 
 | ||||||
|   function getCountry() { |     let dispatch = createEventDispatcher<{ "selected" }>(); | ||||||
|     return tags.data["_country"] |     onDestroy(value.addCallbackD(() => { | ||||||
|   } |         dispatch("selected") | ||||||
|  |     })) | ||||||
|  | 
 | ||||||
|  |     function getCountry() { | ||||||
|  |         return tags.data["_country"] | ||||||
|  |     } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="inline-flex flex-col"> | <div class="inline-flex flex-col"> | ||||||
| 
 | 
 | ||||||
|   {#if config.freeform.inline} |     {#if config.freeform.inline} | ||||||
|     <Inline key={config.freeform.key} {tags} template={config.render}> |         <Inline key={config.freeform.key} {tags} template={config.render}> | ||||||
|       <ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")} |             <ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")} | ||||||
|                       type={config.freeform.type} {placeholder} {value}></ValidatedInput> |                             type={config.freeform.type} {placeholder} {value}></ValidatedInput> | ||||||
|     </Inline> |         </Inline> | ||||||
|   {:else} |     {:else} | ||||||
|     <ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")} |         <ValidatedInput {feedback} {getCountry} on:selected={() => dispatch("selected")} | ||||||
|                     type={config.freeform.type} {placeholder} {value}></ValidatedInput> |                         type={config.freeform.type} {placeholder} {value}></ValidatedInput> | ||||||
| 
 | 
 | ||||||
|   {/if} |     {/if} | ||||||
|   <InputHelper args={config.freeform.helperArgs} {feature} type={config.freeform.type} {value}/> |     <InputHelper args={config.freeform.helperArgs} {feature} type={config.freeform.type} {value}/> | ||||||
| </div> | </div> | ||||||
| 
 |  | ||||||
| {#if $feedback !== undefined} |  | ||||||
|   <div class="alert"> |  | ||||||
|     <Tr t={$feedback} /> |  | ||||||
|   </div> |  | ||||||
| {/if} |  | ||||||
|  |  | ||||||
|  | @ -143,7 +143,7 @@ | ||||||
|                     <TagRenderingQuestion |                     <TagRenderingQuestion | ||||||
|                             config={_firstQuestion} {layer} {selectedElement} {state} {tags} |                             config={_firstQuestion} {layer} {selectedElement} {state} {tags} | ||||||
|                             on:saved={() => {skip(_firstQuestion, true)}}> |                             on:saved={() => {skip(_firstQuestion, true)}}> | ||||||
|                         <button on:click={() => {skip(_firstQuestion)} } |                         <button class="secondary" on:click={() => {skip(_firstQuestion)} } | ||||||
|                                 slot="cancel"> |                                 slot="cancel"> | ||||||
|                             <Tr t={Translations.t.general.skip}></Tr> |                             <Tr t={Translations.t.general.skip}></Tr> | ||||||
|                         </button> |                         </button> | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
|     import type {Feature} from "geojson"; |     import type {Feature} from "geojson"; | ||||||
|     import type {SpecialVisualizationState} from "../../SpecialVisualization"; |     import type {SpecialVisualizationState} from "../../SpecialVisualization"; | ||||||
|     import TagRenderingAnswer from "./TagRenderingAnswer.svelte"; |     import TagRenderingAnswer from "./TagRenderingAnswer.svelte"; | ||||||
|     import {PencilAltIcon} from "@rgossiaux/svelte-heroicons/solid"; |     import {PencilAltIcon, XCircleIcon} from "@rgossiaux/svelte-heroicons/solid"; | ||||||
|     import TagRenderingQuestion from "./TagRenderingQuestion.svelte"; |     import TagRenderingQuestion from "./TagRenderingQuestion.svelte"; | ||||||
|     import {onDestroy} from "svelte"; |     import {onDestroy} from "svelte"; | ||||||
|     import Tr from "../../Base/Tr.svelte"; |     import Tr from "../../Base/Tr.svelte"; | ||||||
|  | @ -33,10 +33,10 @@ | ||||||
|         if (editMode && htmlElem !== undefined) { |         if (editMode && htmlElem !== undefined) { | ||||||
|             // EditMode switched to true, so the person wants to make a change |             // EditMode switched to true, so the person wants to make a change | ||||||
|             // Make sure that the question is in the scrollview! |             // Make sure that the question is in the scrollview! | ||||||
|              | 
 | ||||||
|             // Some delay is applied to give Svelte the time to render the _question_ |             // Some delay is applied to give Svelte the time to render the _question_ | ||||||
|             window.setTimeout(() => { |             window.setTimeout(() => { | ||||||
|                  | 
 | ||||||
|                 Utils.scrollIntoView(htmlElem) |                 Utils.scrollIntoView(htmlElem) | ||||||
|             }, 50) |             }, 50) | ||||||
|         } |         } | ||||||
|  | @ -68,23 +68,28 @@ | ||||||
| 
 | 
 | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div bind:this={htmlElem}> | <div bind:this={htmlElem} class=""> | ||||||
|     {#if config.question && $editingEnabled} |     {#if config.question && $editingEnabled} | ||||||
|         {#if editMode} |         {#if editMode} | ||||||
|             <TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}> |             <div class="m-1 mx-2"> | ||||||
|                 <button slot="cancel" on:click={() => {editMode = false}}> |                 <TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}> | ||||||
|                     <Tr t={Translations.t.general.cancel}/> |                     <button slot="cancel" class="secondary" on:click={() => {editMode = false}}> | ||||||
|                 </button> |                         <Tr t={Translations.t.general.cancel}/> | ||||||
|             </TagRenderingQuestion> |                     </button> | ||||||
|  |                     <XCircleIcon slot="upper-right" class="w-8 h-8" on:click={() => {editMode = false}}/> | ||||||
|  |                 </TagRenderingQuestion> | ||||||
|  |             </div> | ||||||
|         {:else} |         {:else} | ||||||
|             <div class="flex justify-between"> |             <div class="flex justify-between low-interaction items-center m-1 mx-2 p-1 px-2 rounded"> | ||||||
|                 <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/> |                 <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/> | ||||||
|                 <button on:click={() => {editMode = true}} class="shrink-0 w-6 h-6 rounded-full subtle-background p-1"> |                 <button on:click={() => {editMode = true}} class="shrink-0 w-8 h-8 rounded-full p-1 secondary self-start"> | ||||||
|                     <PencilAltIcon/> |                     <PencilAltIcon/> | ||||||
|                 </button> |                 </button> | ||||||
|             </div> |             </div> | ||||||
|         {/if} |         {/if} | ||||||
|     {:else } |     {:else } | ||||||
|         <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/> |         <div class="m-1 p-1 px-2 mx-2"> | ||||||
|  |             <TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/> | ||||||
|  |         </div> | ||||||
|     {/if} |     {/if} | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  | @ -75,7 +75,7 @@ let mappingIsHidden: Store<boolean> = tags.map(tags => { | ||||||
| 
 | 
 | ||||||
| {#if $matchesTerm && !$mappingIsHidden } | {#if $matchesTerm && !$mappingIsHidden } | ||||||
| 
 | 
 | ||||||
|     <label class="flex"> |     <label class={"flex "+ (mappingIsSelected ? "checked": "")}> | ||||||
|         <slot/> |         <slot/> | ||||||
|         <TagRenderingMapping {mapping} {tags} {state} {selectedElement} |         <TagRenderingMapping {mapping} {tags} {state} {selectedElement} | ||||||
|                              {layer}></TagRenderingMapping> |                              {layer}></TagRenderingMapping> | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ | ||||||
|     import {Store, UIEventSource} from "../../../Logic/UIEventSource"; |     import {Store, UIEventSource} from "../../../Logic/UIEventSource"; | ||||||
|     import type {SpecialVisualizationState} from "../../SpecialVisualization"; |     import type {SpecialVisualizationState} from "../../SpecialVisualization"; | ||||||
|     import Tr from "../../Base/Tr.svelte"; |     import Tr from "../../Base/Tr.svelte"; | ||||||
|     import If from "../../Base/If.svelte"; |  | ||||||
|     import type {Feature} from "geojson"; |     import type {Feature} from "geojson"; | ||||||
|     import type {Mapping} from "../../../Models/ThemeConfig/TagRenderingConfig"; |     import type {Mapping} from "../../../Models/ThemeConfig/TagRenderingConfig"; | ||||||
|     import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"; |     import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"; | ||||||
|  | @ -10,15 +9,15 @@ | ||||||
|     import FreeformInput from "./FreeformInput.svelte"; |     import FreeformInput from "./FreeformInput.svelte"; | ||||||
|     import Translations from "../../i18n/Translations.js"; |     import Translations from "../../i18n/Translations.js"; | ||||||
|     import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"; |     import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"; | ||||||
|     import {createEventDispatcher, onDestroy} from "svelte"; |     import {createEventDispatcher} from "svelte"; | ||||||
|     import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; |     import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||||
|     import {ExclamationIcon} from "@rgossiaux/svelte-heroicons/solid"; |  | ||||||
|     import SpecialTranslation from "./SpecialTranslation.svelte"; |     import SpecialTranslation from "./SpecialTranslation.svelte"; | ||||||
|     import TagHint from "../TagHint.svelte"; |     import TagHint from "../TagHint.svelte"; | ||||||
|     import LoginToggle from "../../Base/LoginToggle.svelte"; |     import LoginToggle from "../../Base/LoginToggle.svelte"; | ||||||
|     import SubtleButton from "../../Base/SubtleButton.svelte"; |     import SubtleButton from "../../Base/SubtleButton.svelte"; | ||||||
|     import Loading from "../../Base/Loading.svelte"; |     import Loading from "../../Base/Loading.svelte"; | ||||||
|     import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte"; |     import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte"; | ||||||
|  |     import {Translation} from "../../i18n/Translation"; | ||||||
| 
 | 
 | ||||||
|     export let config: TagRenderingConfig; |     export let config: TagRenderingConfig; | ||||||
|     export let tags: UIEventSource<Record<string, string>>; |     export let tags: UIEventSource<Record<string, string>>; | ||||||
|  | @ -26,13 +25,16 @@ | ||||||
|     export let state: SpecialVisualizationState; |     export let state: SpecialVisualizationState; | ||||||
|     export let layer: LayerConfig; |     export let layer: LayerConfig; | ||||||
| 
 | 
 | ||||||
|  |     let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined); | ||||||
|  |     feedback.addCallbackAndRunD(f => console.trace("Feedback is now", f.txt)) | ||||||
|  | 
 | ||||||
|     // Will be bound if a freeform is available |     // Will be bound if a freeform is available | ||||||
|     let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]); |     let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]); | ||||||
|     let selectedMapping: number = undefined; |     let selectedMapping: number = undefined; | ||||||
|     let checkedMappings: boolean[]; |     let checkedMappings: boolean[]; | ||||||
|     $: { |     $: { | ||||||
|         mappings = config.mappings?.filter(m => { |         mappings = config.mappings?.filter(m => { | ||||||
|             if(typeof m.hideInAnswer === "boolean"){ |             if (typeof m.hideInAnswer === "boolean") { | ||||||
|                 return !m.hideInAnswer |                 return !m.hideInAnswer | ||||||
|             } |             } | ||||||
|             return m.hideInAnswer.matchesProperties(tags.data) |             return m.hideInAnswer.matchesProperties(tags.data) | ||||||
|  | @ -43,9 +45,10 @@ | ||||||
|         } |         } | ||||||
|         if (config.freeform?.key) { |         if (config.freeform?.key) { | ||||||
|             freeformInput.setData(tags.data[config.freeform.key]); |             freeformInput.setData(tags.data[config.freeform.key]); | ||||||
|         }else{ |         } else { | ||||||
|             freeformInput.setData(undefined) |             freeformInput.setData(undefined) | ||||||
|         } |         } | ||||||
|  |         feedback.setData(undefined) | ||||||
|     } |     } | ||||||
|     let selectedTags: TagsFilter = undefined; |     let selectedTags: TagsFilter = undefined; | ||||||
| 
 | 
 | ||||||
|  | @ -108,24 +111,24 @@ | ||||||
|         ).catch(console.error); |         ).catch(console.error); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     let featureSwitchIsTesting = state.featureSwitchIsTesting | ||||||
|  |     let featureSwitchIsDebugging = state.featureSwitches.featureSwitchIsDebugging | ||||||
|  |     let showTags = state.userRelatedState.showTags | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if config.question !== undefined} | {#if config.question !== undefined} | ||||||
|     <div class="border border-black subtle-background flex flex-col"> |     <div class="interactive border-interactive p-1 px-2 flex flex-col"> | ||||||
|         <If condition={state.featureSwitchIsTesting}> |         <div class="flex justify-between"> | ||||||
|             <div class="flex justify-between"> |             <span class="font-bold"> | ||||||
|         <span> |               <SpecialTranslation t={config.question} {tags} {state} {layer} | ||||||
|           <SpecialTranslation t={config.question} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation> |                                   feature={selectedElement}></SpecialTranslation> | ||||||
|         </span> |             </span> | ||||||
|                 <span class="alert">{config.id}</span> |             <slot name="upper-right"/> | ||||||
|             </div> | 
 | ||||||
|             <SpecialTranslation slot="else" t={config.question} {tags} {state} {layer} |         </div> | ||||||
|                                 feature={selectedElement}></SpecialTranslation> |  | ||||||
|         </If> |  | ||||||
| 
 | 
 | ||||||
|         {#if config.questionhint} |         {#if config.questionhint} | ||||||
|             <div class="subtle"> |             <div> | ||||||
|                 <SpecialTranslation t={config.questionhint} {tags} {state} {layer} |                 <SpecialTranslation t={config.questionhint} {tags} {state} {layer} | ||||||
|                                     feature={selectedElement}></SpecialTranslation> |                                     feature={selectedElement}></SpecialTranslation> | ||||||
|             </div> |             </div> | ||||||
|  | @ -140,7 +143,7 @@ | ||||||
| 
 | 
 | ||||||
|         {#if config.freeform?.key && !(mappings?.length > 0)} |         {#if config.freeform?.key && !(mappings?.length > 0)} | ||||||
|             <!-- There are no options to choose from, simply show the input element: fill out the text field --> |             <!-- There are no options to choose from, simply show the input element: fill out the text field --> | ||||||
|             <FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}/> |             <FreeformInput {config} {tags} {feedback} feature={selectedElement} value={freeformInput}/> | ||||||
|         {:else if mappings !== undefined && !config.multiAnswer} |         {:else if mappings !== undefined && !config.multiAnswer} | ||||||
|             <!-- Simple radiobuttons as mapping --> |             <!-- Simple radiobuttons as mapping --> | ||||||
|             <div class="flex flex-col"> |             <div class="flex flex-col"> | ||||||
|  | @ -176,7 +179,7 @@ | ||||||
|                     <label class="flex"> |                     <label class="flex"> | ||||||
|                         <input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+config.mappings?.length} |                         <input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+config.mappings?.length} | ||||||
|                                bind:checked={checkedMappings[config.mappings.length]}> |                                bind:checked={checkedMappings[config.mappings.length]}> | ||||||
|                         <FreeformInput {config} {tags} feature={selectedElement} value={freeformInput} |                         <FreeformInput {config} {tags} {feedback} feature={selectedElement} value={freeformInput} | ||||||
|                                        on:selected={() => checkedMappings[config.mappings.length] = true}/> |                                        on:selected={() => checkedMappings[config.mappings.length] = true}/> | ||||||
|                     </label> |                     </label> | ||||||
|                 {/if} |                 {/if} | ||||||
|  | @ -189,24 +192,34 @@ | ||||||
|                 <img slot="image" src="./assets/svg/login.svg" class="w-8 h-8"/> |                 <img slot="image" src="./assets/svg/login.svg" class="w-8 h-8"/> | ||||||
|                 <Tr t={Translations.t.general.loginToStart} slot="message"></Tr> |                 <Tr t={Translations.t.general.loginToStart} slot="message"></Tr> | ||||||
|             </SubtleButton> |             </SubtleButton> | ||||||
| 
 |             <div class="flex justify-end"> | ||||||
| 
 |  | ||||||
|             <TagHint osmConnection={state.osmConnection} tags={selectedTags}></TagHint> |  | ||||||
|             <div> |  | ||||||
|                 <!-- TagRenderingQuestion-buttons --> |                 <!-- TagRenderingQuestion-buttons --> | ||||||
|                 <slot name="cancel"></slot> |                 {#if $feedback !== undefined} | ||||||
| 
 |                     <div class="alert"> | ||||||
|                 {#if selectedTags !== undefined} |                         <Tr t={$feedback}/> | ||||||
|                     <button on:click={onSave}> |  | ||||||
|                         <Tr t={Translations.t.general.save}></Tr> |  | ||||||
|                     </button> |  | ||||||
|                 {:else } |  | ||||||
|                     <div class="inline-flex w-6 h-6"> |  | ||||||
|                         <!-- Invalid value; show an inactive button or something like that--> |  | ||||||
|                         <ExclamationIcon/> |  | ||||||
|                     </div> |                     </div> | ||||||
|                 {/if} |                 {/if} | ||||||
|  | 
 | ||||||
|  |                 <slot name="cancel"></slot> | ||||||
|  | 
 | ||||||
|  |                 <button on:click={onSave} class={selectedTags === undefined ? "disabled" : "button-shadow"}> | ||||||
|  |                     <Tr t={Translations.t.general.save}></Tr> | ||||||
|  |                 </button> | ||||||
|             </div> |             </div> | ||||||
|  |             {#if $showTags === "yes" || $showTags === "always" || $featureSwitchIsTesting || $featureSwitchIsDebugging} | ||||||
|  |                 <span class="flex justify-between flex-wrap"> | ||||||
|  |                     <TagHint {state} tags={selectedTags}></TagHint> | ||||||
|  |                     <span class="flex flex-wrap"> | ||||||
|  |                         {#if $featureSwitchIsTesting} | ||||||
|  |                         Testmode   | ||||||
|  |                         {/if} | ||||||
|  |                         {#if $featureSwitchIsTesting || $featureSwitchIsDebugging} | ||||||
|  |                             <span class="subtle">{config.id}</span> | ||||||
|  |                         {/if} | ||||||
|  |                             | ||||||
|  |                     </span> | ||||||
|  |                 </span> | ||||||
|  |             {/if} | ||||||
|         </LoginToggle> |         </LoginToggle> | ||||||
|     </div> |     </div> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -63,6 +63,7 @@ export interface SpecialVisualizationState { | ||||||
| 
 | 
 | ||||||
|     readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> |     readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | ||||||
|     readonly userRelatedState: { |     readonly userRelatedState: { | ||||||
|  |         readonly showTags:  UIEventSource<"no" | undefined | "always" | "yes">; | ||||||
|         readonly mangroveIdentity: MangroveIdentity |         readonly mangroveIdentity: MangroveIdentity | ||||||
|         readonly showAllQuestionsAtOnce: UIEventSource<boolean> |         readonly showAllQuestionsAtOnce: UIEventSource<boolean> | ||||||
|         readonly preferencesAsTags: Store<Record<string, string>> |         readonly preferencesAsTags: Store<Record<string, string>> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,9 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     import Svg from "../Svg"; | ||||||
|  |     import Loading from "./Base/Loading.svelte"; | ||||||
|  |     import ToSvelte from "./Base/ToSvelte.svelte"; | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div> | <div> | ||||||
|  | @ -11,11 +14,14 @@ | ||||||
| 
 | 
 | ||||||
|     <div class="normal-background"> |     <div class="normal-background"> | ||||||
|         <h2>Normal background</h2> |         <h2>Normal background</h2> | ||||||
|         There are a few styles, such as the <span class="literal-code">normal-background</span>-style which is used if there is |         There are a few styles, such as the <span class="literal-code">normal-background</span>-style which is used if | ||||||
|  |         there is | ||||||
|         nothing special going on. Some general information, with at most <a href="https://example.com" target="_blank">a |         nothing special going on. Some general information, with at most <a href="https://example.com" target="_blank">a | ||||||
|         link to someplace</a>. |         link to someplace</a>. | ||||||
|         <span class="alert">Alert: something went wrong</span> |         <span class="alert">Alert: something went wrong</span> | ||||||
|         <span class="thanks">Thank you! Operation successful</span> |         <span class="thanks">Thank you! Operation successful</span> | ||||||
|  |         <ToSvelte construct={Svg.login_svg().SetClass("w-12 h-12")}/> | ||||||
|  |         <Loading>Loading...</Loading> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <div class="low-interaction flex flex-col"> |     <div class="low-interaction flex flex-col"> | ||||||
|  | @ -24,35 +30,91 @@ | ||||||
|             There are <span class="literal-code">low-interaction</span> areas, where some buttons might appear. |             There are <span class="literal-code">low-interaction</span> areas, where some buttons might appear. | ||||||
|         </p> |         </p> | ||||||
| 
 | 
 | ||||||
|         <button class="btn">Main action</button> |         <div class="flex"> | ||||||
|         <button class="btn-secondary">Secondary action</button> |             <button> | ||||||
|         <button class="btn-disabled">Disabled</button> |                 <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/> | ||||||
|  |                 Main action | ||||||
|  |             </button> | ||||||
|  |             <button class="disabled"> | ||||||
|  |                 <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/> | ||||||
|  |                 Main action (disabled) | ||||||
|  |             </button> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex"> | ||||||
|  |             <button class="secondary"> | ||||||
|  |                 <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/> | ||||||
|  |                 Secondary action | ||||||
|  |             </button> | ||||||
|  |             <button class="secondary disabled"> | ||||||
|  |                 <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/> | ||||||
|  |                 Secondary action (disabled) | ||||||
|  |             </button> | ||||||
|  |         </div> | ||||||
|         <input type="text"> |         <input type="text"> | ||||||
| 
 | 
 | ||||||
|         <div> |         <div> | ||||||
|             <input id="html" name="fav_language" type="radio" value="HTML"> |             <label for="html" class="checked"> | ||||||
|             <label for="html">HTML</label><br> |                 <input id="html" name="fav_language" type="radio" value="HTML"> | ||||||
|             <input id="css" name="fav_language" type="radio" value="CSS"> |                 HTML (mimicks a <span class="literal-code">checked</span>-element)</label> | ||||||
|             <label for="css">CSS</label><br> |             <label for="css"> | ||||||
|             <input id="javascript" name="fav_language" type="radio" value="JavaScript"> |                 <input id="css" name="fav_language" type="radio" value="CSS"> | ||||||
|             <label for="javascript">JavaScript</label> |                 CSS</label> | ||||||
|  |             <label for="javascript"> | ||||||
|  |                 <input id="javascript" name="fav_language" type="radio" value="JavaScript"> | ||||||
|  |                 <ToSvelte construct={Svg.community_svg().SetClass("w-8 h-8")}/> | ||||||
|  |                 JavaScript</label> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <span class="alert">Alert: something went wrong</span> |         <span class="alert">Alert: something went wrong</span> | ||||||
|         <span class="thanks">Thank you! Operation successful</span> |         <span class="thanks">Thank you! Operation successful</span> | ||||||
|  |         <ToSvelte construct={Svg.login_svg().SetClass("w-12 h-12")}/> | ||||||
|  |         <Loading>Loading...</Loading> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <div class="interactive flex flex-col"> |     <div class="interactive flex flex-col"> | ||||||
|         <h2>Interactive area</h2> |         <h2>Interactive area</h2> | ||||||
|         <p> |         <p> | ||||||
|             There are <span class="literal-code">interactive</span> areas, where some buttons might appear. |             There are <span class="literal-code">interactive</span> areas, where many buttons and input elements | ||||||
|  |             will appear. | ||||||
|         </p> |         </p> | ||||||
| 
 | 
 | ||||||
|         <button class="btn">Main action</button> |         <div class="flex"> | ||||||
|         <button class="btn-secondary">Secondary action</button> |             <button> | ||||||
|         <button class="btn-disabled">Disabled</button> |                 <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/> | ||||||
|  |                 Main action | ||||||
|  |             </button> | ||||||
|  |             <button class="disabled"> | ||||||
|  |                 <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/> | ||||||
|  |                 Main action (disabled) | ||||||
|  |             </button> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex"> | ||||||
|  |             <button class="secondary"> | ||||||
|  |                 <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/> | ||||||
|  |                 Secondary action | ||||||
|  |             </button> | ||||||
|  |             <button class="secondary disabled"> | ||||||
|  |                 <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/> | ||||||
|  |                 Secondary action (disabled) | ||||||
|  |             </button> | ||||||
|  |         </div> | ||||||
|         <span class="alert">Alert: something went wrong</span> |         <span class="alert">Alert: something went wrong</span> | ||||||
|         <span class="thanks">Thank you! Operation successful</span> |         <span class="thanks">Thank you! Operation successful</span> | ||||||
|  |         <ToSvelte construct={Svg.login_svg().SetClass("w-12 h-12")}/> | ||||||
|  |         <Loading>Loading...</Loading> | ||||||
|  |         <div> | ||||||
|  |             <label for="html0"> | ||||||
|  |                 <input id="html0" name="fav_language" type="radio" value="HTML"> | ||||||
|  |                 HTML</label> | ||||||
|  |             <label for="css0"> | ||||||
|  |                 <input id="css0" name="fav_language" type="radio" value="CSS"> | ||||||
|  |                 CSS</label> | ||||||
|  |             <label for="javascript0"> | ||||||
|  | 
 | ||||||
|  |                 <input id="javascript0" name="fav_language" type="radio" value="JavaScript"> | ||||||
|  |                 JavaScript | ||||||
|  |             </label> | ||||||
|  |         </div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								assets/layers/usersettings/license_info.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,24 @@ | ||||||
|  | [ | ||||||
|  |   { | ||||||
|  |     "path": "translate_disabled.svg", | ||||||
|  |     "license": "CC-BY-SA 3.0", | ||||||
|  |     "authors": [ | ||||||
|  |       "MGalloway (WMF)" | ||||||
|  |     ], | ||||||
|  |     "sources": [ | ||||||
|  |       "https://commons.wikimedia.org/wiki/File:OOjs_UI_icon_language-ltr.svg" | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     "path": "translate_mobile.svg", | ||||||
|  |     "license": "CC-BY-SA 3.0", | ||||||
|  |     "authors": [ | ||||||
|  |       "MGalloway (WMF)", | ||||||
|  |       "@ tyskrat" | ||||||
|  |     ], | ||||||
|  |     "sources": [ | ||||||
|  |       "https://commons.wikimedia.org/wiki/File:OOjs_UI_icon_language-ltr.svg", | ||||||
|  |       "https://www.onlinewebfonts.com/icon/1059" | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | ] | ||||||
							
								
								
									
										59
									
								
								assets/layers/usersettings/translate_disabled.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,59 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <svg | ||||||
|  |    width="375px" | ||||||
|  |    height="375px" | ||||||
|  |    viewBox="0 0 375 375" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg7" | ||||||
|  |    sodipodi:docname="translate_disabled.svg" | ||||||
|  |    inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" | ||||||
|  |    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||||
|  |    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <defs | ||||||
|  |      id="defs11" /> | ||||||
|  |   <sodipodi:namedview | ||||||
|  |      id="namedview9" | ||||||
|  |      pagecolor="#505050" | ||||||
|  |      bordercolor="#eeeeee" | ||||||
|  |      borderopacity="1" | ||||||
|  |      inkscape:pageshadow="0" | ||||||
|  |      inkscape:pageopacity="0" | ||||||
|  |      inkscape:pagecheckerboard="0" | ||||||
|  |      showgrid="false" | ||||||
|  |      inkscape:zoom="2.04" | ||||||
|  |      inkscape:cx="187.7451" | ||||||
|  |      inkscape:cy="187.5" | ||||||
|  |      inkscape:window-width="1920" | ||||||
|  |      inkscape:window-height="995" | ||||||
|  |      inkscape:window-x="0" | ||||||
|  |      inkscape:window-y="0" | ||||||
|  |      inkscape:window-maximized="1" | ||||||
|  |      inkscape:current-layer="svg7" /> | ||||||
|  |   <g | ||||||
|  |      id="surface1" | ||||||
|  |      transform="translate(0.37913603,-36.477482)"> | ||||||
|  |     <path | ||||||
|  |        style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" | ||||||
|  |        d="m 178.26953,214.03906 c -24.23047,-23.65625 -45.57812,-44.42187 -57.6914,-92.30859 h 84.80468 V 85.960938 H 121.15234 V 38.652344 H 84.808594 V 86.539062 H 0 v 35.769528 h 86.539062 c 0,0 -0.578124,6.92188 -1.730468,12.11328 C 72.691406,181.73047 58.269531,211.73047 0,241.15234 l 12.117188,35.76953 C 67.5,247.5 96.347656,210.57812 109.03906,169.61719 c 12.11328,31.15234 32.88281,56.53515 56.53906,79.61328 z m 0,0" | ||||||
|  |        id="path2" /> | ||||||
|  |     <path | ||||||
|  |        style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" | ||||||
|  |        d="m 290.19141,98.078125 h -48.46094 l -84.8086,238.269535 h 36.34766 L 217.5,264.80859 h 96.92187 l 24.23047,71.53907 H 375 Z m -60.57422,130.960935 36.34375,-95.1914 36.34765,95.76953 z m 0,0" | ||||||
|  |        id="path4" /> | ||||||
|  |   </g> | ||||||
|  |   <g | ||||||
|  |      id="surface1-3" | ||||||
|  |      transform="matrix(1.3884336,0,0,1.3884336,-154.57939,-335.40822)" | ||||||
|  |      style="fill:#ff0000;fill-opacity:1"> | ||||||
|  |     <path | ||||||
|  |        style="fill:#ff0000;fill-opacity:1;stroke:#b40000;stroke-width:34.2679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" | ||||||
|  |        d="M 364.47831,491.73933 252.63509,379.89612" | ||||||
|  |        id="path826" /> | ||||||
|  |     <path | ||||||
|  |        style="fill:#ff0000;fill-opacity:1;stroke:#b40000;stroke-width:34.2684;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" | ||||||
|  |        d="M 364.64638,379.35739 252.80473,491.199" | ||||||
|  |        id="path828" /> | ||||||
|  |   </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 2.5 KiB | 
							
								
								
									
										50
									
								
								assets/layers/usersettings/translate_mobile.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,50 @@ | ||||||
|  | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
|  | <svg | ||||||
|  |    width="492.01099" | ||||||
|  |    height="848.2948" | ||||||
|  |    viewBox="0 0 492.01099 848.2948" | ||||||
|  |    version="1.1" | ||||||
|  |    id="svg7" | ||||||
|  |    sodipodi:docname="translate_mobile.svg" | ||||||
|  |    inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" | ||||||
|  |    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||||
|  |    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||||
|  |    xmlns="http://www.w3.org/2000/svg" | ||||||
|  |    xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
|  |   <defs | ||||||
|  |      id="defs11" /> | ||||||
|  |   <sodipodi:namedview | ||||||
|  |      id="namedview9" | ||||||
|  |      pagecolor="#505050" | ||||||
|  |      bordercolor="#eeeeee" | ||||||
|  |      borderopacity="1" | ||||||
|  |      inkscape:pageshadow="0" | ||||||
|  |      inkscape:pageopacity="0" | ||||||
|  |      inkscape:pagecheckerboard="0" | ||||||
|  |      showgrid="false" | ||||||
|  |      inkscape:zoom="0.87893548" | ||||||
|  |      inkscape:cx="192.27805" | ||||||
|  |      inkscape:cy="365.78339" | ||||||
|  |      inkscape:window-width="1920" | ||||||
|  |      inkscape:window-height="995" | ||||||
|  |      inkscape:window-x="0" | ||||||
|  |      inkscape:window-y="0" | ||||||
|  |      inkscape:window-maximized="1" | ||||||
|  |      inkscape:current-layer="svg7" /> | ||||||
|  |   <g | ||||||
|  |      id="surface1" | ||||||
|  |      transform="translate(62.542641,230.35576)"> | ||||||
|  |     <path | ||||||
|  |        style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" | ||||||
|  |        d="m 178.26953,214.03906 c -24.23047,-23.65625 -45.57812,-44.42187 -57.6914,-92.30859 h 84.80468 V 85.960938 H 121.15234 V 38.652344 H 84.808594 V 86.539062 H 0 v 35.769528 h 86.539062 c 0,0 -0.578124,6.92188 -1.730468,12.11328 C 72.691406,181.73047 58.269531,211.73047 0,241.15234 l 12.117188,35.76953 C 67.5,247.5 96.347656,210.57812 109.03906,169.61719 c 12.11328,31.15234 32.88281,56.53515 56.53906,79.61328 z m 0,0" | ||||||
|  |        id="path2" /> | ||||||
|  |     <path | ||||||
|  |        style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" | ||||||
|  |        d="m 290.19141,98.078125 h -48.46094 l -84.8086,238.269535 h 36.34766 L 217.5,264.80859 h 96.92187 l 24.23047,71.53907 H 375 Z m -60.57422,130.960935 36.34375,-95.1914 36.34765,95.76953 z m 0,0" | ||||||
|  |        id="path4" /> | ||||||
|  |   </g> | ||||||
|  |   <path | ||||||
|  |      d="M 94.914761,848.29478 H 397.00196 c 52.4529,0 95.00902,-40.3317 95.00902,-90.19258 V 90.25856 C 492.01098,40.44481 449.47371,0 397.09622,0 H 95.009016 C 42.565548,0 0,40.44481 0,90.25856 v 667.83421 c 0,49.84203 42.537271,90.20201 95.009016,90.20201 M 246.09974,817.59594 c -23.49776,0 -42.47129,-19.03951 -42.47129,-42.39589 0,-23.46007 18.94525,-42.43359 42.47129,-42.43359 23.44122,0 42.35819,18.94525 42.35819,42.43359 0,23.38466 -18.94525,42.39589 -42.35819,42.39589 M 181.36543,54.9695 h 129.3838 c 5.24058,0 9.51976,3.60997 9.51976,8.03052 0,4.43941 -4.26033,8.04938 -9.51976,8.04938 h -129.3838 c -5.18403,0 -9.51976,-3.60997 -9.51976,-8.04938 -9.4e-4,-4.42055 4.27918,-8.03052 9.51976,-8.03052 M 37.258993,156.74602 c 0,-7.48384 6.36221,-13.55386 14.21365,-13.55386 H 440.65144 c 7.87029,0 14.20423,6.04174 14.20423,13.55386 v 532.44636 c 0,7.45557 -6.33394,13.50674 -14.20423,13.50674 H 51.472643 c -7.85144,0 -14.21365,-6.05117 -14.21365,-13.50674 V 156.74602" | ||||||
|  |      id="path2-2" | ||||||
|  |      style="stroke-width:9.4255" /> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 2.9 KiB | 
|  | @ -6,6 +6,6 @@ | ||||||
|     <path style="fill:none;stroke-width:2.116667;stroke-linecap:round;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:0.988235;stroke-miterlimit:4;" d="M 25.405787 283.770823 L 23.286365 283.770823 " transform="matrix(14.173228,0,0,14.173228,0.0000135166,-3834.448583)"/> |     <path style="fill:none;stroke-width:2.116667;stroke-linecap:round;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:0.988235;stroke-miterlimit:4;" d="M 25.405787 283.770823 L 23.286365 283.770823 " transform="matrix(14.173228,0,0,14.173228,0.0000135166,-3834.448583)"/> | ||||||
|     <path style="fill:none;stroke-width:2.116667;stroke-linecap:round;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:0.988235;stroke-miterlimit:4;" d="M 13.229166 295.948823 L 13.229166 293.831329 " transform="matrix(14.173228,0,0,14.173228,0.0000135166,-3834.448583)"/> |     <path style="fill:none;stroke-width:2.116667;stroke-linecap:round;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:0.988235;stroke-miterlimit:4;" d="M 13.229166 295.948823 L 13.229166 293.831329 " transform="matrix(14.173228,0,0,14.173228,0.0000135166,-3834.448583)"/> | ||||||
|     <path style="fill:none;stroke-width:2.116667;stroke-linecap:round;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:0.988235;stroke-miterlimit:4;" d="M 13.229166 275.057488 L 13.229166 271.612392 " transform="matrix(14.173228,0,0,14.173228,0.0000135166,-3834.448583)"/> |     <path style="fill:none;stroke-width:2.116667;stroke-linecap:round;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:0.988235;stroke-miterlimit:4;" d="M 13.229166 275.057488 L 13.229166 271.612392 " transform="matrix(14.173228,0,0,14.173228,0.0000135166,-3834.448583)"/> | ||||||
|     <path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 235.789062 187.5 C 235.789062 214.167969 214.167969 235.789062 187.5 235.789062 C 160.832031 235.789062 139.210938 214.167969 139.210938 187.5 C 139.210938 160.832031 160.832031 139.210938 187.5 139.210938 C 214.167969 139.210938 235.789062 160.832031 235.789062 187.5 Z M 235.789062 187.5 "/> |     <path style="stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 235.789062 187.5 C 235.789062 214.167969 214.167969 235.789062 187.5 235.789062 C 160.832031 235.789062 139.210938 214.167969 139.210938 187.5 C 139.210938 160.832031 160.832031 139.210938 187.5 139.210938 C 214.167969 139.210938 235.789062 160.832031 235.789062 187.5 Z M 235.789062 187.5 "/> | ||||||
|   </g> |   </g> | ||||||
| </svg> | </svg> | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB | 
|  | @ -1,6 +1,6 @@ | ||||||
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||||
| <svg xmlns="http://www.w3.org/2000/svg" width="375px" height="375px" viewBox="0 0 375 375" version="1.1"> | <svg xmlns="http://www.w3.org/2000/svg" width="375px" height="375px" viewBox="0 0 375 375" version="1.1"> | ||||||
|   <g id="surface1"> |   <g id="surface1"> | ||||||
|     <path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 47.824219 142.808594 C 31.394531 142.578125 16.113281 151.210938 7.828125 165.40625 C -0.453125 179.597656 -0.453125 197.152344 7.828125 211.34375 C 16.113281 225.535156 31.394531 234.171875 47.824219 233.941406 L 327.667969 233.941406 C 344.101562 234.171875 359.382812 225.535156 367.664062 211.34375 C 375.945312 197.152344 375.945312 179.597656 367.664062 165.40625 C 359.382812 151.210938 344.101562 142.578125 327.667969 142.808594 Z M 47.824219 142.808594 "/> |     <path d="M 47.824219 142.808594 C 31.394531 142.578125 16.113281 151.210938 7.828125 165.40625 C -0.453125 179.597656 -0.453125 197.152344 7.828125 211.34375 C 16.113281 225.535156 31.394531 234.171875 47.824219 233.941406 L 327.667969 233.941406 C 344.101562 234.171875 359.382812 225.535156 367.664062 211.34375 C 375.945312 197.152344 375.945312 179.597656 367.664062 165.40625 C 359.382812 151.210938 344.101562 142.578125 327.667969 142.808594 Z M 47.824219 142.808594 "/> | ||||||
|   </g> |   </g> | ||||||
| </svg> | </svg> | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 751 B After Width: | Height: | Size: 678 B | 
|  | @ -1,7 +1,7 @@ | ||||||
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||||
| <svg xmlns="http://www.w3.org/2000/svg" width="375px" height="375px" viewBox="0 0 375 375" version="1.1"> | <svg xmlns="http://www.w3.org/2000/svg" width="375px" height="375px" viewBox="0 0 375 375" version="1.1"> | ||||||
|   <g id="surface1"> |   <g id="surface1"> | ||||||
|     <path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 46.554688 142.492188 C 30.003906 142.261719 14.609375 150.957031 6.269531 165.253906 C -2.074219 179.550781 -2.074219 197.234375 6.269531 211.53125 C 14.609375 225.828125 30.003906 234.523438 46.554688 234.292969 L 328.445312 234.292969 C 344.996094 234.523438 360.390625 225.828125 368.734375 211.53125 C 377.074219 197.234375 377.074219 179.550781 368.734375 165.253906 C 360.390625 150.957031 344.996094 142.261719 328.445312 142.492188 Z M 46.554688 142.492188 "/> |     <path d="M 46.554688 142.492188 C 30.003906 142.261719 14.609375 150.957031 6.269531 165.253906 C -2.074219 179.550781 -2.074219 197.234375 6.269531 211.53125 C 14.609375 225.828125 30.003906 234.523438 46.554688 234.292969 L 328.445312 234.292969 C 344.996094 234.523438 360.390625 225.828125 368.734375 211.53125 C 377.074219 197.234375 377.074219 179.550781 368.734375 165.253906 C 360.390625 150.957031 344.996094 142.261719 328.445312 142.492188 Z M 46.554688 142.492188 "/> | ||||||
|     <path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 186.34375 0.0078125 C 161.007812 0.386719 140.769531 21.21875 141.128906 46.554688 L 141.128906 328.445312 C 140.898438 344.996094 149.59375 360.390625 163.890625 368.734375 C 178.1875 377.074219 195.871094 377.074219 210.167969 368.734375 C 224.464844 360.390625 233.160156 344.996094 232.929688 328.445312 L 232.929688 46.554688 C 233.105469 34.152344 228.253906 22.203125 219.476562 13.433594 C 210.699219 4.664062 198.75 -0.175781 186.34375 0.0078125 Z M 186.34375 0.0078125 "/> |     <path d="M 186.34375 0.0078125 C 161.007812 0.386719 140.769531 21.21875 141.128906 46.554688 L 141.128906 328.445312 C 140.898438 344.996094 149.59375 360.390625 163.890625 368.734375 C 178.1875 377.074219 195.871094 377.074219 210.167969 368.734375 C 224.464844 360.390625 233.160156 344.996094 232.929688 328.445312 L 232.929688 46.554688 C 233.105469 34.152344 228.253906 22.203125 219.476562 13.433594 C 210.699219 4.664062 198.75 -0.175781 186.34375 0.0078125 Z M 186.34375 0.0078125 "/> | ||||||
|   </g> |   </g> | ||||||
| </svg> | </svg> | ||||||
|  |  | ||||||
| Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										514
									
								
								index.css
									
										
									
									
									
								
							
							
						
						|  | @ -26,28 +26,38 @@ | ||||||
|     --alert-color: #fee4d1; |     --alert-color: #fee4d1; | ||||||
|     --alert-foreground-color: var(--foreground-color); |     --alert-foreground-color: var(--foreground-color); | ||||||
| 
 | 
 | ||||||
|  |     --low-interaction-background: #eeeeee; | ||||||
|  |     --low-interaction-foreground: black; | ||||||
|  |     --low-interaction-contrast: #ff00ff; | ||||||
|  | 
 | ||||||
|  |     --interactive-background: #dddddd; | ||||||
|  |     --interactive-foreground: black; | ||||||
|  |     --interactive-contrast: #ff00ff; | ||||||
|  | 
 | ||||||
|  |     --button-background: #737373; | ||||||
|  |     --button-foreground: white; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Base colour of interactive elements, mainly the 'subtle button' |      * Base colour of interactive elements, mainly the 'subtle button' | ||||||
|  |      * @deprecated | ||||||
|      */ |      */ | ||||||
|     --subtle-detail-color: #dbeafe; |     --subtle-detail-color: #dbeafe; | ||||||
|     --subtle-detail-color-contrast: black; |     --subtle-detail-color-contrast: black; | ||||||
|     --subtle-detail-color-light-contrast: lightgrey; |     --subtle-detail-color-light-contrast: lightgrey; | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|        * A stronger variant of the 'subtle-detail-colour' |  | ||||||
|        * Used as subtle button hover |  | ||||||
|        */ |  | ||||||
|     --unsubtle-detail-color: #bfdbfe; |  | ||||||
|     --unsubtle-detail-color-contrast: black; |  | ||||||
| 
 | 
 | ||||||
|     --catch-detail-color: #3a3aeb; |     --catch-detail-color: black; /*#3a3aeb;*/ | ||||||
|     --catch-detail-color-contrast: white; |     --catch-detail-foregroundcolor: white; | ||||||
|  |     --catch-detail-color-contrast: #fb3afb; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     --image-carousel-height: 350px; |     --image-carousel-height: 350px; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /***********************************************************************\ | ||||||
|  | * Various tweaks and settings to make some behaviours more predictable * | ||||||
|  | \***********************************************************************/ | ||||||
| html, | html, | ||||||
| body { | body { | ||||||
|     height: 100%; |     height: 100%; | ||||||
|  | @ -60,7 +70,6 @@ body { | ||||||
|     font-family: "Helvetica Neue", Arial, sans-serif; |     font-family: "Helvetica Neue", Arial, sans-serif; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| svg, | svg, | ||||||
| img { | img { | ||||||
|     box-sizing: content-box; |     box-sizing: content-box; | ||||||
|  | @ -68,97 +77,16 @@ img { | ||||||
|     height: 100%; |     height: 100%; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .no-images img { |  | ||||||
|     /* Used solely in 'imageAttribution' */ |  | ||||||
|     display: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .text-white a { |  | ||||||
|     /* Used solely in 'imageAttribution' */ |  | ||||||
|     color: var(--background-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| .weblate-link { |  | ||||||
|     /* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */ |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| a { |  | ||||||
|     color: var(--foreground-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| .btn { |  | ||||||
|     line-height: 1.25rem; |  | ||||||
|     --tw-text-opacity: 1; |  | ||||||
|     color: var(--catch-detail-color-contrast); |  | ||||||
|     --tw-bg-opacity: 1; |  | ||||||
|     background-color: var(--catch-detail-color); |  | ||||||
|     display: inline-flex; |  | ||||||
|     border-radius: 1.5rem; |  | ||||||
|     padding-top: 0.75rem; |  | ||||||
|     padding-bottom: 0.75rem; |  | ||||||
|     padding-left: 1.25rem; |  | ||||||
|     padding-right: 1.25rem; |  | ||||||
|     font-size: large; |  | ||||||
|     font-weight: bold; |  | ||||||
|     transition: 100ms; |  | ||||||
|     /*-- invisible border: rendered on hover*/ |  | ||||||
|     border: 3px solid var(--unsubtle-detail-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .btn:hover { |  | ||||||
|     border: 3px solid var(--catch-detail-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .btn-secondary { |  | ||||||
|     background-color: var(--catch-detail-color); |  | ||||||
|     filter: saturate(0.5); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .btn-secondary:hover { |  | ||||||
|     background-color: var(--catch-detail-color); |  | ||||||
|     filter: unset; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .btn-disabled { |  | ||||||
|     filter: saturate(0.3); |  | ||||||
|     cursor: default; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .btn-disabled:hover { |  | ||||||
|     border: 3px solid var(--unsubtle-detail-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .rounded-left-full { |  | ||||||
|     border-bottom-left-radius: 999rem; |  | ||||||
|     border-top-left-radius: 999rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .rounded-right-full { |  | ||||||
|     border-bottom-right-radius: 999rem; |  | ||||||
|     border-top-right-radius: 999rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .link-underline a { |  | ||||||
|     text-decoration: underline 1px var(--foreground-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| a.link-underline { |  | ||||||
|     text-decoration: underline 1px var(--foreground-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .link-no-underline a { |  | ||||||
|     text-decoration: none; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| li { | li { | ||||||
|     margin-left: 0.5em; |     margin-left: 0.5em; | ||||||
|     padding-left: 0.2em; |     padding-left: 0.2em; | ||||||
|     margin-top: 0.1em; |     margin-top: 0.1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | li::marker { | ||||||
|  |     content: "•"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| h1 { | h1 { | ||||||
|     font-size: x-large; |     font-size: x-large; | ||||||
|     margin-top: 0.6em; |     margin-top: 0.6em; | ||||||
|  | @ -181,21 +109,22 @@ h3 { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| h3 { |  | ||||||
|     font-size: larger; |  | ||||||
|     margin-top: 0.6em; |  | ||||||
|     margin-bottom: 0; |  | ||||||
|     font-weight: bolder; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| p { | p { | ||||||
|     padding-top: 0.1em; |     padding-top: 0.1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| li::marker { | input { | ||||||
|     content: "•"; |     color: var(--foreground-color); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /************************* BIG CATEGORIES ********************************/ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * The main classes that dictate the structure of the entire app, | ||||||
|  |  * and some interactive elements | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| .subtle-background { | .subtle-background { | ||||||
|     background: var(--subtle-detail-color); |     background: var(--subtle-detail-color); | ||||||
|     color: var(--subtle-detail-color-contrast); |     color: var(--subtle-detail-color-contrast); | ||||||
|  | @ -206,10 +135,291 @@ li::marker { | ||||||
|     color: var(--foreground-color); |     color: var(--foreground-color); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .low-interaction { | ||||||
|  |     background: var(--low-interaction-background); | ||||||
|  |     color: var(--low-interaction-foreground) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .interactive { | ||||||
|  |     background: var(--interactive-background); | ||||||
|  |     color: var(--interactive-foreground) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .border-interactive { | ||||||
|  |     border: 2px dashed var(--catch-detail-color-contrast); | ||||||
|  |     border-radius: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /******************* Styling of input elements **********************/ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * This very important section defines what the various input elements look like within the 'low-interaction' and 'interactive'-blocks | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | button.disabled { | ||||||
|  |     cursor: default; | ||||||
|  |     border: 2px dashed var(--button-background); | ||||||
|  |     background: unset; | ||||||
|  |     color: unset; | ||||||
|  |     box-shadow: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.disabled:hover { | ||||||
|  |     cursor: default; | ||||||
|  |     border: 2px dashed var(--button-background); | ||||||
|  |     background: unset; | ||||||
|  |     color: unset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | button:hover { | ||||||
|  |     border: 2px solid var(--catch-detail-color-contrast); | ||||||
|  |     background-color: var(--catch-detail-color); | ||||||
|  |     color: var(--catch-detail-foregroundcolor); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button:hover img { | ||||||
|  |     background: var(--low-interaction-background); | ||||||
|  |     border-radius: 100rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | button { | ||||||
|  |     display: inline-flex; | ||||||
|  |     line-height: 1.25rem; | ||||||
|  |     margin: 0.2rem; | ||||||
|  |     padding: 0.4rem; | ||||||
|  |     padding-left: 0.6rem; | ||||||
|  |     padding-right: 0.6rem; | ||||||
|  |     font-size: large; | ||||||
|  |     font-weight: bold; | ||||||
|  |     color: var(--button-foreground); | ||||||
|  |     background: var(--button-background); | ||||||
|  |     /*-- invisible border: rendered on hover*/ | ||||||
|  |     border: 2px solid var(--button-background); | ||||||
|  |     border-radius: 0.5rem; | ||||||
|  |     transition: all 250ms; | ||||||
|  |     --tw-text-opacity: 1; | ||||||
|  |     --tw-bg-opacity: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button .button-shadow { | ||||||
|  |     box-shadow: 0 5px 10px #88888888; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.selected { | ||||||
|  |     background-color: var(--catch-detail-color); | ||||||
|  |     border-color: var(--catch-detail-color); | ||||||
|  | 
 | ||||||
|  |     color: var(--catch-detail-foregroundcolor); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.selected svg path { | ||||||
|  |     fill: var(--catch-detail-foregroundcolor) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button svg path { | ||||||
|  |     fill: var(--button-foreground) !important;; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .interactive button.disabled svg path { | ||||||
|  |     fill: var(--interactive-foreground) !important;; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .low-interaction button.disabled svg path { | ||||||
|  |     fill: var(--low-interaction-foreground) !important;; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .normal-background button.disabled svg path { | ||||||
|  |     fill: var(--foreground-color) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.disabled.secondary:hover { | ||||||
|  |     background: unset; | ||||||
|  |     color: unset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary { | ||||||
|  |     background: var(--low-interaction-background); | ||||||
|  |     color: var(--low-interaction-foreground); | ||||||
|  |     border-color: var(--button-background); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .interactive button.secondary { | ||||||
|  |     background: var(--interactive-background); | ||||||
|  |     color: var(--interactive-foreground); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary svg path { | ||||||
|  |     fill: var(--low-interaction-foreground) !important; | ||||||
|  |     transition: all 250ms; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary.disabled { | ||||||
|  |     background: unset; | ||||||
|  |     color: var(--low-interaction-foreground); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary.disabled svg path { | ||||||
|  |     fill: var(--low-interaction-foreground) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary:hover { | ||||||
|  |     background-color: var(--catch-detail-color); | ||||||
|  |     color: var(--catch-detail-foregroundcolor); | ||||||
|  |     border-color: var(--catch-detail-color-contrast); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary:hover svg path { | ||||||
|  |     fill: var(--catch-detail-foregroundcolor) !important;; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary.disabled:hover svg path { | ||||||
|  |     fill: var(--low-interaction-foreground) !important;; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label { | ||||||
|  |     /** | ||||||
|  |      * Label should _contain_ the input element | ||||||
|  |      */ | ||||||
|  |     border: 2px solid var(--interactive-background); | ||||||
|  |     padding: 0.25rem; | ||||||
|  |     padding-right: 0.5rem; | ||||||
|  |     padding-left: 0.5rem; | ||||||
|  |     border-radius: 0.5rem; | ||||||
|  |     background-color: var(--low-interaction-background); | ||||||
|  |     width: 100%; | ||||||
|  |     display: block; | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     transition: all 250ms; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label:hover { | ||||||
|  |     background-color: var(--catch-detail-color); | ||||||
|  |     color: var(--catch-detail-foregroundcolor); | ||||||
|  |     border: 2px solid var(--interactive-contrast) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label img { | ||||||
|  |     padding: 0.25rem; | ||||||
|  |     border-radius: 0.25rem; | ||||||
|  |     background: var(--low-interaction-background); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label svg path { | ||||||
|  |     transition: all 250ms; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label:hover svg path { | ||||||
|  |     fill: var(--catch-detail-foregroundcolor) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label.checked { | ||||||
|  |     border: 2px solid var(--foreground-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /************************* OTHER CATEGORIES ********************************/ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Smaller categories which convey some semantic information but don't define bigger blocks. | ||||||
|  |  * As they are _semantic_ categories, they can be styled | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | .thanks { | ||||||
|  |     /* The class to indicate 'operation successful' or 'thank you for contributing' */ | ||||||
|  |     background-color: #43d904; | ||||||
|  |     font-weight: bold; | ||||||
|  |     border-radius: 1em; | ||||||
|  |     margin: 0.25em; | ||||||
|  |     text-align: center; | ||||||
|  |     padding: 0.15em 0.3em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .alert { | ||||||
|  |     /* The class to convey important information, e.g. 'invalid', 'something went wrong', 'warning: testmode', ... */ | ||||||
|  |     background-color: var(--alert-color); | ||||||
|  |     color: var(--alert-foreground-color); | ||||||
|  |     font-weight: bold; | ||||||
|  |     border-radius: 1em; | ||||||
|  |     margin: 0.25em; | ||||||
|  |     text-align: center; | ||||||
|  |     padding: 0.15em 0.3em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .subtle { | ||||||
|  |     /* For all information that is not important for 99% of the users */ | ||||||
|  |     color: #999; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .link-underline .subtle a { | ||||||
|  |     text-decoration: underline 1px #7193bb88; | ||||||
|  |     color: #7193bb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .literal-code { | ||||||
|  |     /* A codeblock */ | ||||||
|  |     display: inline-block; | ||||||
|  |     background-color: lightgray; | ||||||
|  |     padding: 0.1rem; | ||||||
|  |     padding-left: 0.35rem; | ||||||
|  |     padding-right: 0.35rem; | ||||||
|  |     word-break: break-word; | ||||||
|  |     color: black; | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     font-family: monospace; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .interactive .literal-code { | ||||||
|  |     background-color: #b3b3b3; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /************************** UTILITY ************************/ | ||||||
|  | 
 | ||||||
|  | /**  | ||||||
|  |  * Utility classes are there for a specific function to pin down browser behaviour (and cannot be changed) | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .text-white a { | ||||||
|  |     /* Used solely in 'imageAttribution'  and in many themes*/ | ||||||
|  |     color: var(--background-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .block-ruby { | .block-ruby { | ||||||
|     display: block ruby; |     display: block ruby; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | .rounded-left-full { | ||||||
|  |     border-bottom-left-radius: 999rem; | ||||||
|  |     border-top-left-radius: 999rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .rounded-right-full { | ||||||
|  |     border-bottom-right-radius: 999rem; | ||||||
|  |     border-top-right-radius: 999rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .no-images img { | ||||||
|  |     /* Used solely in 'imageAttribution' and in many themes for the label*/ | ||||||
|  |     display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .link-underline a { | ||||||
|  |     text-decoration: underline 1px var(--foreground-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | a.link-underline { | ||||||
|  |     text-decoration: underline 1px var(--foreground-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .link-no-underline a { | ||||||
|  |     text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .disable-links a { | .disable-links a { | ||||||
|     pointer-events: none; |     pointer-events: none; | ||||||
|     text-decoration: none !important; |     text-decoration: none !important; | ||||||
|  | @ -229,6 +439,13 @@ li::marker { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | .zebra-table tr:nth-child(even) { | ||||||
|  |     background-color: #f2f2f2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /************************* MISC ELEMENTS *************************/ | ||||||
|  | 
 | ||||||
| .selected svg:not(.noselect *) path.selectable { | .selected svg:not(.noselect *) path.selectable { | ||||||
|     /* A marker on the map gets the 'selected' class when it's properties are displayed |     /* A marker on the map gets the 'selected' class when it's properties are displayed | ||||||
|     */ |     */ | ||||||
|  | @ -246,7 +463,6 @@ li::marker { | ||||||
|     overflow: visible !important; |     overflow: visible !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| @-webkit-keyframes glowing-drop-shadow { | @-webkit-keyframes glowing-drop-shadow { | ||||||
|     from { |     from { | ||||||
|         filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); |         filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); | ||||||
|  | @ -256,37 +472,6 @@ li::marker { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| /**************** GENERIC ****************/ |  | ||||||
| 
 |  | ||||||
| .alert { |  | ||||||
|     background-color: var(--alert-color); |  | ||||||
|     color: var(--alert-foreground-color); |  | ||||||
|     font-weight: bold; |  | ||||||
|     border-radius: 1em; |  | ||||||
|     margin: 0.25em; |  | ||||||
|     text-align: center; |  | ||||||
|     padding: 0.15em 0.3em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .subtle { |  | ||||||
|     color: #999; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .link-underline .subtle a { |  | ||||||
|     text-decoration: underline 1px #7193bb88; |  | ||||||
|     color: #7193bb; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .thanks { |  | ||||||
|     background-color: #43d904; |  | ||||||
|     font-weight: bold; |  | ||||||
|     border-radius: 1em; |  | ||||||
|     margin: 0.25em; |  | ||||||
|     text-align: center; |  | ||||||
|     padding: 0.15em 0.3em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @keyframes slide { | @keyframes slide { | ||||||
|     /* This is the animation on the marker to add a new point - it slides through all the possible presets */ |     /* This is the animation on the marker to add a new point - it slides through all the possible presets */ | ||||||
|     from { |     from { | ||||||
|  | @ -298,48 +483,6 @@ li::marker { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| /***************** Info box (box containing features and questions ******************/ |  | ||||||
| 
 |  | ||||||
| input { |  | ||||||
|     color: var(--foreground-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .literal-code { |  | ||||||
|     display: inline-block; |  | ||||||
|     background-color: lightgray; |  | ||||||
|     padding: 0.5em; |  | ||||||
|     word-break: break-word; |  | ||||||
|     color: black; |  | ||||||
|     box-sizing: border-box; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** Switch layout **/ |  | ||||||
| .small-image img { |  | ||||||
|     height: 1em; |  | ||||||
|     max-width: 1em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .small-image { |  | ||||||
|     height: 1em; |  | ||||||
|     max-width: 1em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .slideshow-item img { |  | ||||||
|     height: var(--image-carousel-height); |  | ||||||
|     width: unset; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .animate-height { |  | ||||||
|     transition: max-height 0.5s ease-in-out; |  | ||||||
|     overflow-y: hidden; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .zebra-table tr:nth-child(even) { |  | ||||||
|     background-color: #f2f2f2; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| .glowing-shadow { | .glowing-shadow { | ||||||
|     -webkit-animation: glowing 1s ease-in-out infinite alternate; |     -webkit-animation: glowing 1s ease-in-out infinite alternate; | ||||||
|     -moz-animation: glowing 1s ease-in-out infinite alternate; |     -moz-animation: glowing 1s ease-in-out infinite alternate; | ||||||
|  | @ -355,3 +498,20 @@ input { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | /************************* LEGACY MARKER - CLEANUP BELOW ********************************/ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .slideshow-item img { | ||||||
|  |     /* Legacy: should be replace when the image element is ported to Svelte*/ | ||||||
|  |     height: var(--image-carousel-height); | ||||||
|  |     width: unset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .animate-height { | ||||||
|  |     /* Legacy: should be replaced by headlessui disclosure in time */ | ||||||
|  |     transition: max-height 0.5s ease-in-out; | ||||||
|  |     overflow-y: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ | ||||||
|     "start": "npm run generate:layeroverview && npm run strt", |     "start": "npm run generate:layeroverview && npm run strt", | ||||||
|     "strt": "vite --host", |     "strt": "vite --host", | ||||||
|     "strttest": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve test.html assets/templates/*.svg assets/templates/fonts/*.ttf", |     "strttest": "export NODE_OPTIONS=--max_old_space_size=8364 && parcel serve test.html assets/templates/*.svg assets/templates/fonts/*.ttf", | ||||||
|     "watch:css": "tailwindcss -i index.css -o css/index-tailwind-output.css --watch", |     "watch:css": "tailwindcss -i index.css -o public/css/index-tailwind-output.css --watch", | ||||||
|     "generate:css": "tailwindcss -i index.css -o public/css/index-tailwind-output.css", |     "generate:css": "tailwindcss -i index.css -o public/css/index-tailwind-output.css", | ||||||
|     "generate:doctests": "doctest-ts-improved . --ignore .*.spec.ts --ignore .*ConfigJson.ts", |     "generate:doctests": "doctest-ts-improved . --ignore .*.spec.ts --ignore .*ConfigJson.ts", | ||||||
|     "test:run-only": "vitest --run test", |     "test:run-only": "vitest --run test", | ||||||
|  | @ -45,7 +45,7 @@ | ||||||
|     "weblate-add-upstream": "git remote add weblate-github git@github.com:weblate/MapComplete.git && git remote add weblate-hosted-core https://hosted.weblate.org/git/mapcomplete/core/ && git remote add weblate-hosted-layers https://hosted.weblate.org/git/mapcomplete/layers/", |     "weblate-add-upstream": "git remote add weblate-github git@github.com:weblate/MapComplete.git && git remote add weblate-hosted-core https://hosted.weblate.org/git/mapcomplete/core/ && git remote add weblate-hosted-layers https://hosted.weblate.org/git/mapcomplete/layers/", | ||||||
|     "weblate-merge": "git remote update weblate-github; git merge weblate-github/weblate-mapcomplete-core weblate-github/weblate-mapcomplete-layers weblate-github/weblate-mapcomplete-layer-translations", |     "weblate-merge": "git remote update weblate-github; git merge weblate-github/weblate-mapcomplete-core weblate-github/weblate-mapcomplete-layers weblate-github/weblate-mapcomplete-layer-translations", | ||||||
|     "weblate-fix-heavy": "git fetch weblate-hosted-layers; git fetch weblate-hosted-core; git merge weblate-hosted-layers/master weblate-hosted-core/master ", |     "weblate-fix-heavy": "git fetch weblate-hosted-layers; git fetch weblate-hosted-core; git merge weblate-hosted-layers/master weblate-hosted-core/master ", | ||||||
|     "housekeeping": "git pull && npm run weblate-fix-heavy && npm run generate && npm run generate:docs && npm run generate:contributor-list && vite-node scripts/fetchLanguages.ts && npm run format && git add assets/ langs/ Docs/ **/*.ts Docs/* && git commit -m 'chore: automated housekeeping...'", |     "housekeeping": "git pull && npx update-browserslist-db@latest && npm run weblate-fix-heavy && npm run generate && npm run generate:docs && npm run generate:contributor-list && vite-node scripts/fetchLanguages.ts && npm run format && git add assets/ langs/ Docs/ **/*.ts Docs/* && git commit -m 'chore: automated housekeeping...'", | ||||||
|     "parseSchools": "vite-node scripts/schools/amendSchoolData.ts" |     "parseSchools": "vite-node scripts/schools/amendSchoolData.ts" | ||||||
|   }, |   }, | ||||||
|   "keywords": [ |   "keywords": [ | ||||||
|  |  | ||||||
|  | @ -825,6 +825,11 @@ video { | ||||||
|   margin-bottom: 0.75rem; |   margin-bottom: 0.75rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .mx-2 { | ||||||
|  |   margin-left: 0.5rem; | ||||||
|  |   margin-right: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .mr-2 { | .mr-2 { | ||||||
|   margin-right: 0.5rem; |   margin-right: 0.5rem; | ||||||
| } | } | ||||||
|  | @ -1055,6 +1060,10 @@ video { | ||||||
|   max-height: 6rem; |   max-height: 6rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .max-h-12 { | ||||||
|  |   max-height: 3rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .min-h-\[8rem\] { | .min-h-\[8rem\] { | ||||||
|   min-height: 8rem; |   min-height: 8rem; | ||||||
| } | } | ||||||
|  | @ -1281,6 +1290,10 @@ video { | ||||||
|   row-gap: 0.25rem; |   row-gap: 0.25rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .self-start { | ||||||
|  |   align-self: flex-start; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .self-end { | .self-end { | ||||||
|   align-self: flex-end; |   align-self: flex-end; | ||||||
| } | } | ||||||
|  | @ -1307,6 +1320,10 @@ video { | ||||||
|   white-space: nowrap; |   white-space: nowrap; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .overflow-ellipsis { | ||||||
|  |   text-overflow: ellipsis; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .text-ellipsis { | .text-ellipsis { | ||||||
|   text-overflow: ellipsis; |   text-overflow: ellipsis; | ||||||
| } | } | ||||||
|  | @ -1443,11 +1460,6 @@ video { | ||||||
|   background-color: rgb(219 234 254 / var(--tw-bg-opacity)); |   background-color: rgb(219 234 254 / var(--tw-bg-opacity)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .bg-gray-300 { |  | ||||||
|   --tw-bg-opacity: 1; |  | ||||||
|   background-color: rgb(209 213 219 / var(--tw-bg-opacity)); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .bg-black { | .bg-black { | ||||||
|   --tw-bg-opacity: 1; |   --tw-bg-opacity: 1; | ||||||
|   background-color: rgb(0 0 0 / var(--tw-bg-opacity)); |   background-color: rgb(0 0 0 / var(--tw-bg-opacity)); | ||||||
|  | @ -1463,6 +1475,11 @@ video { | ||||||
|   background-color: rgb(224 231 255 / var(--tw-bg-opacity)); |   background-color: rgb(224 231 255 / var(--tw-bg-opacity)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .bg-gray-300 { | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
|  |   background-color: rgb(209 213 219 / var(--tw-bg-opacity)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .bg-red-500 { | .bg-red-500 { | ||||||
|   --tw-bg-opacity: 1; |   --tw-bg-opacity: 1; | ||||||
|   background-color: rgb(239 68 68 / var(--tw-bg-opacity)); |   background-color: rgb(239 68 68 / var(--tw-bg-opacity)); | ||||||
|  | @ -1792,6 +1809,12 @@ video { | ||||||
|   transition-duration: 150ms; |   transition-duration: 150ms; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .transition-shadow { | ||||||
|  |   transition-property: box-shadow; | ||||||
|  |   transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | ||||||
|  |   transition-duration: 150ms; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .ease-in-out { | .ease-in-out { | ||||||
|   transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); |   transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | ||||||
| } | } | ||||||
|  | @ -1816,23 +1839,32 @@ video { | ||||||
|   /* A colour scheme to indicate an error or warning */ |   /* A colour scheme to indicate an error or warning */ | ||||||
|   --alert-color: #fee4d1; |   --alert-color: #fee4d1; | ||||||
|   --alert-foreground-color: var(--foreground-color); |   --alert-foreground-color: var(--foreground-color); | ||||||
|  |   --low-interaction-background: #eeeeee; | ||||||
|  |   --low-interaction-foreground: black; | ||||||
|  |   --low-interaction-contrast: #ff00ff; | ||||||
|  |   --interactive-background: #dddddd; | ||||||
|  |   --interactive-foreground: black; | ||||||
|  |   --interactive-contrast: #ff00ff; | ||||||
|  |   --button-background: #737373; | ||||||
|  |   --button-foreground: white; | ||||||
|   /** |   /** | ||||||
|      * Base colour of interactive elements, mainly the 'subtle button' |      * Base colour of interactive elements, mainly the 'subtle button' | ||||||
|  |      * @deprecated | ||||||
|      */ |      */ | ||||||
|   --subtle-detail-color: #dbeafe; |   --subtle-detail-color: #dbeafe; | ||||||
|   --subtle-detail-color-contrast: black; |   --subtle-detail-color-contrast: black; | ||||||
|   --subtle-detail-color-light-contrast: lightgrey; |   --subtle-detail-color-light-contrast: lightgrey; | ||||||
|   /** |   --catch-detail-color: black; | ||||||
|        * A stronger variant of the 'subtle-detail-colour' |   /*#3a3aeb;*/ | ||||||
|        * Used as subtle button hover |   --catch-detail-foregroundcolor: white; | ||||||
|        */ |   --catch-detail-color-contrast: #fb3afb; | ||||||
|   --unsubtle-detail-color: #bfdbfe; |  | ||||||
|   --unsubtle-detail-color-contrast: black; |  | ||||||
|   --catch-detail-color: #3a3aeb; |  | ||||||
|   --catch-detail-color-contrast: white; |  | ||||||
|   --image-carousel-height: 350px; |   --image-carousel-height: 350px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /***********************************************************************\ | ||||||
|  | * Various tweaks and settings to make some behaviours more predictable * | ||||||
|  | \***********************************************************************/ | ||||||
|  | 
 | ||||||
| html, | html, | ||||||
| body { | body { | ||||||
|   height: 100%; |   height: 100%; | ||||||
|  | @ -1852,67 +1884,311 @@ img { | ||||||
|   height: 100%; |   height: 100%; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .no-images img { | li { | ||||||
|   /* Used solely in 'imageAttribution' */ |   margin-left: 0.5em; | ||||||
|   display: none; |   padding-left: 0.2em; | ||||||
|  |   margin-top: 0.1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .text-white a { | li::marker { | ||||||
|   /* Used solely in 'imageAttribution' */ |   content: "•"; | ||||||
|   color: var(--background-color); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .weblate-link { | h1 { | ||||||
|   /* Weblate-links are the little translation icon next to translatable sentences. Due to their special nature, they are exempt from some rules */ |   font-size: x-large; | ||||||
|  |   margin-top: 0.6em; | ||||||
|  |   margin-bottom: 0.4em; | ||||||
|  |   font-weight: bold; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| a { | h2 { | ||||||
|  |   font-size: large; | ||||||
|  |   margin-top: 0.5em; | ||||||
|  |   margin-bottom: 0.3em; | ||||||
|  |   font-weight: bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | h3 { | ||||||
|  |   font-size: larger; | ||||||
|  |   margin-top: 0.6em; | ||||||
|  |   margin-bottom: 0; | ||||||
|  |   font-weight: bold; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | p { | ||||||
|  |   padding-top: 0.1em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input { | ||||||
|   color: var(--foreground-color); |   color: var(--foreground-color); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .btn { | /************************* BIG CATEGORIES ********************************/ | ||||||
|   line-height: 1.25rem; | 
 | ||||||
|   --tw-text-opacity: 1; | /** | ||||||
|   color: var(--catch-detail-color-contrast); |  * The main classes that dictate the structure of the entire app, | ||||||
|   --tw-bg-opacity: 1; |  * and some interactive elements | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | .subtle-background { | ||||||
|  |   background: var(--subtle-detail-color); | ||||||
|  |   color: var(--subtle-detail-color-contrast); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .normal-background { | ||||||
|  |   background: var(--background-color); | ||||||
|  |   color: var(--foreground-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .low-interaction { | ||||||
|  |   background: var(--low-interaction-background); | ||||||
|  |   color: var(--low-interaction-foreground) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .interactive { | ||||||
|  |   background: var(--interactive-background); | ||||||
|  |   color: var(--interactive-foreground) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .border-interactive { | ||||||
|  |   border: 2px dashed var(--catch-detail-color-contrast); | ||||||
|  |   border-radius: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /******************* Styling of input elements **********************/ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * This very important section defines what the various input elements look like within the 'low-interaction' and 'interactive'-blocks | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | button.disabled { | ||||||
|  |   cursor: default; | ||||||
|  |   border: 2px dashed var(--button-background); | ||||||
|  |   background: unset; | ||||||
|  |   color: unset; | ||||||
|  |   box-shadow: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.disabled:hover { | ||||||
|  |   cursor: default; | ||||||
|  |   border: 2px dashed var(--button-background); | ||||||
|  |   background: unset; | ||||||
|  |   color: unset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button:hover { | ||||||
|  |   border: 2px solid var(--catch-detail-color-contrast); | ||||||
|   background-color: var(--catch-detail-color); |   background-color: var(--catch-detail-color); | ||||||
|  |   color: var(--catch-detail-foregroundcolor); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button:hover img { | ||||||
|  |   background: var(--low-interaction-background); | ||||||
|  |   border-radius: 100rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button { | ||||||
|   display: inline-flex; |   display: inline-flex; | ||||||
|   border-radius: 1.5rem; |   line-height: 1.25rem; | ||||||
|   padding-top: 0.75rem; |   margin: 0.2rem; | ||||||
|   padding-bottom: 0.75rem; |   padding: 0.4rem; | ||||||
|   padding-left: 1.25rem; |   padding-left: 0.6rem; | ||||||
|   padding-right: 1.25rem; |   padding-right: 0.6rem; | ||||||
|   font-size: large; |   font-size: large; | ||||||
|   font-weight: bold; |   font-weight: bold; | ||||||
|   transition: 100ms; |   color: var(--button-foreground); | ||||||
|  |   background: var(--button-background); | ||||||
|   /*-- invisible border: rendered on hover*/ |   /*-- invisible border: rendered on hover*/ | ||||||
|   border: 3px solid var(--unsubtle-detail-color); |   border: 2px solid var(--button-background); | ||||||
|  |   border-radius: 0.5rem; | ||||||
|  |   transition: all 250ms; | ||||||
|  |   --tw-text-opacity: 1; | ||||||
|  |   --tw-bg-opacity: 1; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .btn:hover { | button .button-shadow { | ||||||
|   border: 3px solid var(--catch-detail-color); |   box-shadow: 0 5px 10px #88888888; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .btn-secondary { | button.selected { | ||||||
|   background-color: var(--catch-detail-color); |   background-color: var(--catch-detail-color); | ||||||
|   -webkit-filter: saturate(0.5); |   border-color: var(--catch-detail-color); | ||||||
|           filter: saturate(0.5); |   color: var(--catch-detail-foregroundcolor); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .btn-secondary:hover { | button.selected svg path { | ||||||
|  |   fill: var(--catch-detail-foregroundcolor) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button svg path { | ||||||
|  |   fill: var(--button-foreground) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .interactive button.disabled svg path { | ||||||
|  |   fill: var(--interactive-foreground) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .low-interaction button.disabled svg path { | ||||||
|  |   fill: var(--low-interaction-foreground) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .normal-background button.disabled svg path { | ||||||
|  |   fill: var(--foreground-color) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.disabled.secondary:hover { | ||||||
|  |   background: unset; | ||||||
|  |   color: unset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary { | ||||||
|  |   background: var(--low-interaction-background); | ||||||
|  |   color: var(--low-interaction-foreground); | ||||||
|  |   border-color: var(--button-background); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .interactive button.secondary { | ||||||
|  |   background: var(--interactive-background); | ||||||
|  |   color: var(--interactive-foreground); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary svg path { | ||||||
|  |   fill: var(--low-interaction-foreground) !important; | ||||||
|  |   transition: all 250ms; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary.disabled { | ||||||
|  |   background: unset; | ||||||
|  |   color: var(--low-interaction-foreground); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary.disabled svg path { | ||||||
|  |   fill: var(--low-interaction-foreground) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.secondary:hover { | ||||||
|   background-color: var(--catch-detail-color); |   background-color: var(--catch-detail-color); | ||||||
|   -webkit-filter: unset; |   color: var(--catch-detail-foregroundcolor); | ||||||
|           filter: unset; |   border-color: var(--catch-detail-color-contrast); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .btn-disabled { | button.secondary:hover svg path { | ||||||
|   -webkit-filter: saturate(0.3); |   fill: var(--catch-detail-foregroundcolor) !important; | ||||||
|           filter: saturate(0.3); |  | ||||||
|   cursor: default; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .btn-disabled:hover { | button.secondary.disabled:hover svg path { | ||||||
|   border: 3px solid var(--unsubtle-detail-color); |   fill: var(--low-interaction-foreground) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label { | ||||||
|  |   /** | ||||||
|  |      * Label should _contain_ the input element | ||||||
|  |      */ | ||||||
|  |   border: 2px solid var(--interactive-background); | ||||||
|  |   padding: 0.25rem; | ||||||
|  |   padding-right: 0.5rem; | ||||||
|  |   padding-left: 0.5rem; | ||||||
|  |   border-radius: 0.5rem; | ||||||
|  |   background-color: var(--low-interaction-background); | ||||||
|  |   width: 100%; | ||||||
|  |   display: block; | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   transition: all 250ms; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label:hover { | ||||||
|  |   background-color: var(--catch-detail-color); | ||||||
|  |   color: var(--catch-detail-foregroundcolor); | ||||||
|  |   border: 2px solid var(--interactive-contrast) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label img { | ||||||
|  |   padding: 0.25rem; | ||||||
|  |   border-radius: 0.25rem; | ||||||
|  |   background: var(--low-interaction-background); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label svg path { | ||||||
|  |   transition: all 250ms; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label:hover svg path { | ||||||
|  |   fill: var(--catch-detail-foregroundcolor) !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label.checked { | ||||||
|  |   border: 2px solid var(--foreground-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /************************* OTHER CATEGORIES ********************************/ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Smaller categories which convey some semantic information but don't define bigger blocks. | ||||||
|  |  * As they are _semantic_ categories, they can be styled | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | .thanks { | ||||||
|  |   /* The class to indicate 'operation successful' or 'thank you for contributing' */ | ||||||
|  |   background-color: #43d904; | ||||||
|  |   font-weight: bold; | ||||||
|  |   border-radius: 1em; | ||||||
|  |   margin: 0.25em; | ||||||
|  |   text-align: center; | ||||||
|  |   padding: 0.15em 0.3em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .alert { | ||||||
|  |   /* The class to convey important information, e.g. 'invalid', 'something went wrong', 'warning: testmode', ... */ | ||||||
|  |   background-color: var(--alert-color); | ||||||
|  |   color: var(--alert-foreground-color); | ||||||
|  |   font-weight: bold; | ||||||
|  |   border-radius: 1em; | ||||||
|  |   margin: 0.25em; | ||||||
|  |   text-align: center; | ||||||
|  |   padding: 0.15em 0.3em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .subtle { | ||||||
|  |   /* For all information that is not important for 99% of the users */ | ||||||
|  |   color: #999; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .link-underline .subtle a { | ||||||
|  |   -webkit-text-decoration: underline 1px #7193bb88; | ||||||
|  |           text-decoration: underline 1px #7193bb88; | ||||||
|  |   color: #7193bb; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .literal-code { | ||||||
|  |   /* A codeblock */ | ||||||
|  |   display: inline-block; | ||||||
|  |   background-color: lightgray; | ||||||
|  |   padding: 0.1rem; | ||||||
|  |   padding-left: 0.35rem; | ||||||
|  |   padding-right: 0.35rem; | ||||||
|  |   word-break: break-word; | ||||||
|  |   color: black; | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   font-family: monospace; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .interactive .literal-code { | ||||||
|  |   background-color: #b3b3b3; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /************************** UTILITY ************************/ | ||||||
|  | 
 | ||||||
|  | /**  | ||||||
|  |  * Utility classes are there for a specific function to pin down browser behaviour (and cannot be changed) | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | .text-white a { | ||||||
|  |   /* Used solely in 'imageAttribution'  and in many themes*/ | ||||||
|  |   color: var(--background-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .block-ruby { | ||||||
|  |   display: block ruby; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .rounded-left-full { | .rounded-left-full { | ||||||
|  | @ -1925,6 +2201,11 @@ a { | ||||||
|   border-top-right-radius: 999rem; |   border-top-right-radius: 999rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .no-images img { | ||||||
|  |   /* Used solely in 'imageAttribution' and in many themes for the label*/ | ||||||
|  |   display: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .link-underline a { | .link-underline a { | ||||||
|   -webkit-text-decoration: underline 1px var(--foreground-color); |   -webkit-text-decoration: underline 1px var(--foreground-color); | ||||||
|           text-decoration: underline 1px var(--foreground-color); |           text-decoration: underline 1px var(--foreground-color); | ||||||
|  | @ -1939,51 +2220,6 @@ a.link-underline { | ||||||
|   text-decoration: none; |   text-decoration: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| li { |  | ||||||
|   margin-left: 0.5em; |  | ||||||
|   padding-left: 0.2em; |  | ||||||
|   margin-top: 0.1em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| h2 { |  | ||||||
|   font-size: x-large; |  | ||||||
|   margin-bottom: 0. 4em; |  | ||||||
|   font-size: large; |  | ||||||
|   margin-top: 0.5em; |  | ||||||
|   margin-bottom: 0.3em; |  | ||||||
|   font-weight: bold; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| h3 { |  | ||||||
|   font-weight: bold; |  | ||||||
|   font-size: larger; |  | ||||||
|   margin-top: 0.6em; |  | ||||||
|   margin-bottom: 0; |  | ||||||
|   font-weight: bolder; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| p { |  | ||||||
|   padding-top: 0.1em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| li::marker { |  | ||||||
|   content: "•"; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .subtle-background { |  | ||||||
|   background: var(--subtle-detail-color); |  | ||||||
|   color: var(--subtle-detail-color-contrast); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .normal-background { |  | ||||||
|   background: var(--background-color); |  | ||||||
|   color: var(--foreground-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .block-ruby { |  | ||||||
|   display: block ruby; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .disable-links a { | .disable-links a { | ||||||
|   pointer-events: none; |   pointer-events: none; | ||||||
|   text-decoration: none !important; |   text-decoration: none !important; | ||||||
|  | @ -2002,6 +2238,12 @@ li::marker { | ||||||
|   display: none; |   display: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .zebra-table tr:nth-child(even) { | ||||||
|  |   background-color: #f2f2f2; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /************************* MISC ELEMENTS *************************/ | ||||||
|  | 
 | ||||||
| .selected svg:not(.noselect *) path.selectable { | .selected svg:not(.noselect *) path.selectable { | ||||||
|   /* A marker on the map gets the 'selected' class when it's properties are displayed |   /* A marker on the map gets the 'selected' class when it's properties are displayed | ||||||
|     */ |     */ | ||||||
|  | @ -2030,41 +2272,6 @@ li::marker { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**************** GENERIC ****************/ |  | ||||||
| 
 |  | ||||||
| .alert { |  | ||||||
|   background-color: var(--alert-color); |  | ||||||
|   color: var(--alert-foreground-color); |  | ||||||
|   font-weight: bold; |  | ||||||
|   border-radius: 1em; |  | ||||||
|   margin: 0.25em; |  | ||||||
|   text-align: center; |  | ||||||
|   padding: 0.15em 0.3em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .shadow { |  | ||||||
|   box-shadow: 0 0 20px var(--shadow-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .subtle { |  | ||||||
|   color: #999; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .link-underline .subtle a { |  | ||||||
|   -webkit-text-decoration: underline 1px #7193bb88; |  | ||||||
|           text-decoration: underline 1px #7193bb88; |  | ||||||
|   color: #7193bb; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .thanks { |  | ||||||
|   background-color: #43d904; |  | ||||||
|   font-weight: bold; |  | ||||||
|   border-radius: 1em; |  | ||||||
|   margin: 0.25em; |  | ||||||
|   text-align: center; |  | ||||||
|   padding: 0.15em 0.3em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @-webkit-keyframes slide { | @-webkit-keyframes slide { | ||||||
|   /* This is the animation on the marker to add a new point - it slides through all the possible presets */ |   /* This is the animation on the marker to add a new point - it slides through all the possible presets */ | ||||||
| 
 | 
 | ||||||
|  | @ -2093,47 +2300,6 @@ li::marker { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /***************** Info box (box containing features and questions ******************/ |  | ||||||
| 
 |  | ||||||
| input { |  | ||||||
|   color: var(--foreground-color); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .literal-code { |  | ||||||
|   display: inline-block; |  | ||||||
|   background-color: lightgray; |  | ||||||
|   padding: 0.5em; |  | ||||||
|   word-break: break-word; |  | ||||||
|   color: black; |  | ||||||
|   box-sizing: border-box; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** Switch layout **/ |  | ||||||
| 
 |  | ||||||
| .small-image img { |  | ||||||
|   height: 1em; |  | ||||||
|   max-width: 1em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .small-image { |  | ||||||
|   height: 1em; |  | ||||||
|   max-width: 1em; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .slideshow-item img { |  | ||||||
|   height: var(--image-carousel-height); |  | ||||||
|   width: unset; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .animate-height { |  | ||||||
|   transition: max-height 0.5s ease-in-out; |  | ||||||
|   overflow-y: hidden; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .zebra-table tr:nth-child(even) { |  | ||||||
|   background-color: #f2f2f2; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .glowing-shadow { | .glowing-shadow { | ||||||
|   -webkit-animation: glowing 1s ease-in-out infinite alternate; |   -webkit-animation: glowing 1s ease-in-out infinite alternate; | ||||||
|   animation: glowing 1s ease-in-out infinite alternate; |   animation: glowing 1s ease-in-out infinite alternate; | ||||||
|  | @ -2149,6 +2315,20 @@ input { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /************************* LEGACY MARKER - CLEANUP BELOW ********************************/ | ||||||
|  | 
 | ||||||
|  | .slideshow-item img { | ||||||
|  |   /* Legacy: should be replace when the image element is ported to Svelte*/ | ||||||
|  |   height: var(--image-carousel-height); | ||||||
|  |   width: unset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .animate-height { | ||||||
|  |   /* Legacy: should be replaced by headlessui disclosure in time */ | ||||||
|  |   transition: max-height 0.5s ease-in-out; | ||||||
|  |   overflow-y: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .hover\:bg-unsubtle:hover { | .hover\:bg-unsubtle:hover { | ||||||
|   --tw-bg-opacity: 1; |   --tw-bg-opacity: 1; | ||||||
|   background-color: rgb(191 219 254 / var(--tw-bg-opacity)); |   background-color: rgb(191 219 254 / var(--tw-bg-opacity)); | ||||||
|  | @ -2400,3 +2580,4 @@ input { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |  | ||||||