Remove unused packages, configurations, and assets

This commit is contained in:
Snider 2025-10-26 00:02:40 +01:00
parent 95d5ad1bcd
commit d077831825
182 changed files with 19669 additions and 1076 deletions

13
Makefile Normal file
View file

@ -0,0 +1,13 @@
.PHONY: prod-docs
all:
(cd cmd/app && wails3 build)
development-docs:
@echo "Running development documentation Website..."
@(cd pkg/core/docs && mkdocs serve -w src)
prod-docs:
@echo "Generating documentation tp Repo Root..."
@(cd pkg/core/docs && mkdocs build -d public && cp -r src public)
@echo "Documentation generated at docs/index.html"

View file

@ -1,35 +0,0 @@
version: '3'
# This top-level Taskfile orchestrates tasks in the sub-directories.
# It uses the 'dir' property to ensure that each included task runs
# from its correct working directory.
includes:
app:
dir: {{.TASKFILE_DIR}}/cmd/app
docs:
dir: {{.TASKFILE_DIR}}/pkg/v1/core/docs
tasks:
default:
desc: "Show available tasks."
cmds:
- task --list-all
build:
desc: "Build both the application and the documentation."
cmds:
- task: app:build
- task: docs:build
dev:
desc: "Run the application in development mode and serve the documentation."
cmds:
- task: app:dev
- task: docs:dev
tidy:
desc: "Tidy all Go modules in the workspace."
cmds:
- go mod tidy

View file

@ -2,14 +2,14 @@ version: '3'
includes:
common: "{{.TASKFILE_DIR}}/build/Taskfile.yml"
windows: "{{.TASKFILE_DIR}}/build/windows/Taskfile.yml"
darwin: "{{.TASKFILE_DIR}}/build/darwin/Taskfile.yml"
linux: "{{.TASKFILE_DIR}}/build/linux/Taskfile.yml"
common: "./build/Taskfile.yml"
windows: "./build/windows/Taskfile.yml"
darwin: "./build/darwin/Taskfile.yml"
linux: "./build/linux/Taskfile.yml"
vars:
APP_NAME: "core"
BIN_DIR: "{{.TASKFILE_DIR}}/build/bin"
BIN_DIR: "./build/bin"
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
tasks:
@ -31,4 +31,4 @@ tasks:
dev:
summary: Runs the application in development mode
cmds:
- wails3 dev -config {{.TASKFILE_DIR}}/build/config.yml -port {{.VITE_PORT}}
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}

View file

@ -45,6 +45,7 @@ tasks:
generate:bindings:
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
summary: Generates bindings
dir: ../
deps:
- task: go:mod:tidy
sources:

View file

@ -5,8 +5,8 @@ version: '3'
# This information is used to generate the build assets.
info:
companyName: "Snider"
productName: "Core.Framework"
productIdentifier: "com.core.desktop"
productName: "Core.App"
productIdentifier: "com.core.app"
description: "A program that does demos the features"
copyright: "(c) EUPL-1.2, Snider"
comments: "Demo Dev Area"

View file

@ -1,7 +1,7 @@
version: '3'
includes:
common: {{.TASKFILE_DIR}}/Taskfile.yml
common: ../Taskfile.yml
tasks:
build:

View file

@ -1,7 +1,7 @@
version: '3'
includes:
common: "{{.TASKFILE_DIR}}/Taskfile.yml"
common: "../Taskfile.yml"
tasks:
build:

View file

@ -1,7 +1,7 @@
version: '3'
includes:
common: {{.TASKFILE_DIR}}/Taskfile.yml
common: ../Taskfile.yml
tasks:
build:

View file

@ -21,14 +21,16 @@ func main() {
},
})
app.RegisterService(application.NewService(core.Service(
core.WithWails(app), // Provides the Wails application instance to core services
core.WithAssets(assets), // Provides the embed.FS to core services
core.WithService(config.Register), // Provides the ability to persist UI state (windows reopen where they closed)
core.WithService(display.Register), // Provides the ability to open windows
core.WithService(crypt.Register), // Provides cryptographic functions
core.WithServiceLock(), // locks core from accepting new services blocking access to IPC
)))
coreService := core.New(
core.WithWails(app),
core.WithAssets(assets),
core.WithService(config.New),
core.WithService(display.New),
core.WithService(crypt.New),
core.WithServiceLock(),
)
app.RegisterService(application.NewService(coreService))
err := app.Run()
if err != nil {

View file

@ -0,0 +1,40 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
export function ACTION(msg: $models.Message | null): $CancellablePromise<void> {
return $Call.ByID(4097389530, msg);
}
export function Core(): $CancellablePromise<$models.Core | null> {
return $Call.ByID(590325359).then(($result: any) => {
return $$createType1($result);
});
}
export function RegisterAction(handler: any): $CancellablePromise<void> {
return $Call.ByID(2751902507, handler);
}
export function RegisterActions(...handlers: any[]): $CancellablePromise<void> {
return $Call.ByID(2391561096, handlers);
}
export function RegisterService(name: string, api: any): $CancellablePromise<void> {
return $Call.ByID(3677749144, name, api);
}
export function Service(name: string): $CancellablePromise<any> {
return $Call.ByID(3861253011, name);
}
// Private type creation functions
const $$createType0 = $models.Core.createFrom;
const $$createType1 = $Create.Nullable($$createType0);

View file

@ -0,0 +1,15 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as Core from "./core.js";
export {
Core
};
export {
Core
} from "./models.js";
export type {
Message
} from "./models.js";

View file

@ -0,0 +1,41 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../../wailsapp/wails/v3/pkg/application/models.js";
export class Core {
"App": application$0.App | null;
/** Creates a new Core instance. */
constructor($$source: Partial<Core> = {}) {
if (!("App" in $$source)) {
this["App"] = null;
}
Object.assign(this, $$source);
}
/**
* Creates a new Core instance from a string or object.
*/
static createFrom($$source: any = {}): Core {
const $$createField0_0 = $$createType1;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("App" in $$parsedSource) {
$$parsedSource["App"] = $$createField0_0($$parsedSource["App"]);
}
return new Core($$parsedSource as Partial<Core>);
}
}
export type Message = any;
// Private type creation functions
const $$createType0 = application$0.App.createFrom;
const $$createType1 = $Create.Nullable($$createType0);

View file

@ -0,0 +1,17 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export {
App,
BrowserManager,
ClipboardManager,
ContextMenuManager,
DialogManager,
EnvironmentManager,
EventManager,
KeyBindingManager,
MenuManager,
ScreenManager,
SystemTrayManager,
WindowManager
} from "./models.js";

View file

@ -0,0 +1,369 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as slog$0 from "../../../../../../log/slog/models.js";
export class App {
/**
* Manager pattern for organized API
*/
"Window": WindowManager | null;
"ContextMenu": ContextMenuManager | null;
"KeyBinding": KeyBindingManager | null;
"Browser": BrowserManager | null;
"Env": EnvironmentManager | null;
"Dialog": DialogManager | null;
"Event": EventManager | null;
"Menu": MenuManager | null;
"Screen": ScreenManager | null;
"Clipboard": ClipboardManager | null;
"SystemTray": SystemTrayManager | null;
"Logger": slog$0.Logger | null;
/** Creates a new App instance. */
constructor($$source: Partial<App> = {}) {
if (!("Window" in $$source)) {
this["Window"] = null;
}
if (!("ContextMenu" in $$source)) {
this["ContextMenu"] = null;
}
if (!("KeyBinding" in $$source)) {
this["KeyBinding"] = null;
}
if (!("Browser" in $$source)) {
this["Browser"] = null;
}
if (!("Env" in $$source)) {
this["Env"] = null;
}
if (!("Dialog" in $$source)) {
this["Dialog"] = null;
}
if (!("Event" in $$source)) {
this["Event"] = null;
}
if (!("Menu" in $$source)) {
this["Menu"] = null;
}
if (!("Screen" in $$source)) {
this["Screen"] = null;
}
if (!("Clipboard" in $$source)) {
this["Clipboard"] = null;
}
if (!("SystemTray" in $$source)) {
this["SystemTray"] = null;
}
if (!("Logger" in $$source)) {
this["Logger"] = null;
}
Object.assign(this, $$source);
}
/**
* Creates a new App instance from a string or object.
*/
static createFrom($$source: any = {}): App {
const $$createField0_0 = $$createType1;
const $$createField1_0 = $$createType3;
const $$createField2_0 = $$createType5;
const $$createField3_0 = $$createType7;
const $$createField4_0 = $$createType9;
const $$createField5_0 = $$createType11;
const $$createField6_0 = $$createType13;
const $$createField7_0 = $$createType15;
const $$createField8_0 = $$createType17;
const $$createField9_0 = $$createType19;
const $$createField10_0 = $$createType21;
const $$createField11_0 = $$createType23;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("Window" in $$parsedSource) {
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
}
if ("ContextMenu" in $$parsedSource) {
$$parsedSource["ContextMenu"] = $$createField1_0($$parsedSource["ContextMenu"]);
}
if ("KeyBinding" in $$parsedSource) {
$$parsedSource["KeyBinding"] = $$createField2_0($$parsedSource["KeyBinding"]);
}
if ("Browser" in $$parsedSource) {
$$parsedSource["Browser"] = $$createField3_0($$parsedSource["Browser"]);
}
if ("Env" in $$parsedSource) {
$$parsedSource["Env"] = $$createField4_0($$parsedSource["Env"]);
}
if ("Dialog" in $$parsedSource) {
$$parsedSource["Dialog"] = $$createField5_0($$parsedSource["Dialog"]);
}
if ("Event" in $$parsedSource) {
$$parsedSource["Event"] = $$createField6_0($$parsedSource["Event"]);
}
if ("Menu" in $$parsedSource) {
$$parsedSource["Menu"] = $$createField7_0($$parsedSource["Menu"]);
}
if ("Screen" in $$parsedSource) {
$$parsedSource["Screen"] = $$createField8_0($$parsedSource["Screen"]);
}
if ("Clipboard" in $$parsedSource) {
$$parsedSource["Clipboard"] = $$createField9_0($$parsedSource["Clipboard"]);
}
if ("SystemTray" in $$parsedSource) {
$$parsedSource["SystemTray"] = $$createField10_0($$parsedSource["SystemTray"]);
}
if ("Logger" in $$parsedSource) {
$$parsedSource["Logger"] = $$createField11_0($$parsedSource["Logger"]);
}
return new App($$parsedSource as Partial<App>);
}
}
/**
* BrowserManager manages browser-related operations
*/
export class BrowserManager {
/** Creates a new BrowserManager instance. */
constructor($$source: Partial<BrowserManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new BrowserManager instance from a string or object.
*/
static createFrom($$source: any = {}): BrowserManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new BrowserManager($$parsedSource as Partial<BrowserManager>);
}
}
/**
* ClipboardManager manages clipboard operations
*/
export class ClipboardManager {
/** Creates a new ClipboardManager instance. */
constructor($$source: Partial<ClipboardManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new ClipboardManager instance from a string or object.
*/
static createFrom($$source: any = {}): ClipboardManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new ClipboardManager($$parsedSource as Partial<ClipboardManager>);
}
}
/**
* ContextMenuManager manages all context menu operations
*/
export class ContextMenuManager {
/** Creates a new ContextMenuManager instance. */
constructor($$source: Partial<ContextMenuManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new ContextMenuManager instance from a string or object.
*/
static createFrom($$source: any = {}): ContextMenuManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new ContextMenuManager($$parsedSource as Partial<ContextMenuManager>);
}
}
/**
* DialogManager manages dialog-related operations
*/
export class DialogManager {
/** Creates a new DialogManager instance. */
constructor($$source: Partial<DialogManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new DialogManager instance from a string or object.
*/
static createFrom($$source: any = {}): DialogManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new DialogManager($$parsedSource as Partial<DialogManager>);
}
}
/**
* EnvironmentManager manages environment-related operations
*/
export class EnvironmentManager {
/** Creates a new EnvironmentManager instance. */
constructor($$source: Partial<EnvironmentManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new EnvironmentManager instance from a string or object.
*/
static createFrom($$source: any = {}): EnvironmentManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new EnvironmentManager($$parsedSource as Partial<EnvironmentManager>);
}
}
/**
* EventManager manages event-related operations
*/
export class EventManager {
/** Creates a new EventManager instance. */
constructor($$source: Partial<EventManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new EventManager instance from a string or object.
*/
static createFrom($$source: any = {}): EventManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new EventManager($$parsedSource as Partial<EventManager>);
}
}
/**
* KeyBindingManager manages all key binding operations
*/
export class KeyBindingManager {
/** Creates a new KeyBindingManager instance. */
constructor($$source: Partial<KeyBindingManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new KeyBindingManager instance from a string or object.
*/
static createFrom($$source: any = {}): KeyBindingManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new KeyBindingManager($$parsedSource as Partial<KeyBindingManager>);
}
}
/**
* MenuManager manages menu-related operations
*/
export class MenuManager {
/** Creates a new MenuManager instance. */
constructor($$source: Partial<MenuManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new MenuManager instance from a string or object.
*/
static createFrom($$source: any = {}): MenuManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new MenuManager($$parsedSource as Partial<MenuManager>);
}
}
export class ScreenManager {
/** Creates a new ScreenManager instance. */
constructor($$source: Partial<ScreenManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new ScreenManager instance from a string or object.
*/
static createFrom($$source: any = {}): ScreenManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new ScreenManager($$parsedSource as Partial<ScreenManager>);
}
}
/**
* SystemTrayManager manages system tray-related operations
*/
export class SystemTrayManager {
/** Creates a new SystemTrayManager instance. */
constructor($$source: Partial<SystemTrayManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new SystemTrayManager instance from a string or object.
*/
static createFrom($$source: any = {}): SystemTrayManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new SystemTrayManager($$parsedSource as Partial<SystemTrayManager>);
}
}
/**
* WindowManager manages all window-related operations
*/
export class WindowManager {
/** Creates a new WindowManager instance. */
constructor($$source: Partial<WindowManager> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new WindowManager instance from a string or object.
*/
static createFrom($$source: any = {}): WindowManager {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new WindowManager($$parsedSource as Partial<WindowManager>);
}
}
// Private type creation functions
const $$createType0 = WindowManager.createFrom;
const $$createType1 = $Create.Nullable($$createType0);
const $$createType2 = ContextMenuManager.createFrom;
const $$createType3 = $Create.Nullable($$createType2);
const $$createType4 = KeyBindingManager.createFrom;
const $$createType5 = $Create.Nullable($$createType4);
const $$createType6 = BrowserManager.createFrom;
const $$createType7 = $Create.Nullable($$createType6);
const $$createType8 = EnvironmentManager.createFrom;
const $$createType9 = $Create.Nullable($$createType8);
const $$createType10 = DialogManager.createFrom;
const $$createType11 = $Create.Nullable($$createType10);
const $$createType12 = EventManager.createFrom;
const $$createType13 = $Create.Nullable($$createType12);
const $$createType14 = MenuManager.createFrom;
const $$createType15 = $Create.Nullable($$createType14);
const $$createType16 = ScreenManager.createFrom;
const $$createType17 = $Create.Nullable($$createType16);
const $$createType18 = ClipboardManager.createFrom;
const $$createType19 = $Create.Nullable($$createType18);
const $$createType20 = SystemTrayManager.createFrom;
const $$createType21 = $Create.Nullable($$createType20);
const $$createType22 = slog$0.Logger.createFrom;
const $$createType23 = $Create.Nullable($$createType22);

View file

@ -0,0 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export {
Logger
} from "./models.js";

View file

@ -0,0 +1,31 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "@wailsio/runtime";
/**
* A Logger records structured information about each call to its
* Log, Debug, Info, Warn, and Error methods.
* For each call, it creates a [Record] and passes it to a [Handler].
*
* To create a new Logger, call [New] or a Logger method
* that begins "With".
*/
export class Logger {
/** Creates a new Logger instance. */
constructor($$source: Partial<Logger> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new Logger instance from a string or object.
*/
static createFrom($$source: any = {}): Logger {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new Logger($$parsedSource as Partial<Logger>);
}
}

View file

@ -0,0 +1,39 @@
version: '3'
# This file contains the build logic specifically for the macOS (Darwin) platform.
includes:
common: ./common.yml
tasks:
build:
desc: "Builds the application for macOS."
dir: "{{.APP_ROOT}}"
deps:
- task: common:go:mod:tidy
- task: common:build:public
vars:
PRODUCTION: '{{.PRODUCTION | default "false"}}'
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o bin/{{.APP_NAME}}
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
env:
GOOS: darwin
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default "amd64"}}'
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
desc: "Packages the application as a .app bundle for macOS."
dir: "{{.APP_ROOT}}"
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- echo "Packaging for macOS..."
# This command assumes Wails handles the bundling process.
# You may need to adjust based on your specific packaging tool.
- wails3 build -package -platform darwin/{{.ARCH | default "amd64"}} -production -clean -o "{{.APP_NAME}}.app"

40
cmd/tasks/build-linux.yml Normal file
View file

@ -0,0 +1,40 @@
version: '3'
# This file contains the build logic specifically for the Linux platform.
includes:
common: ./common.yml
tasks:
build:
desc: "Builds the application for Linux."
dir: "{{.APP_ROOT}}"
deps:
- task: common:go:mod:tidy
- task: common:build:public
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o bin/{{.APP_NAME}}
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
env:
GOOS: linux
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default "amd64"}}'
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
desc: "Packages the application for Linux."
dir: "{{.APP_ROOT}}"
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- echo "Packaging for Linux... (AppImage, Deb, RPM)"
# Placeholder for actual packaging commands

View file

@ -0,0 +1,39 @@
version: '3'
# This file contains the build logic specifically for the Windows platform.
includes:
common: ./common.yml
tasks:
build:
desc: "Builds the application for Windows."
dir: "{{.APP_ROOT}}"
deps:
- task: common:go:mod:tidy
- task: common:build:public
vars:
PRODUCTION: '{{.PRODUCTION | default "false"}}'
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o bin/{{.APP_NAME}}.exe
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H=windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
env:
GOOS: windows
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default "amd64"}}'
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
desc: "Packages the application as a .exe installer for Windows."
dir: "{{.APP_ROOT}}"
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- echo "Packaging for Windows..."
# This command assumes Wails handles the packaging process, potentially using NSIS or MSIX.
# You may need to adjust based on your specific packaging tool.
- wails3 build -package -platform windows/{{.ARCH | default "amd6d64"}} -production -clean -o "{{.APP_NAME}}.exe"

2
cmd/tasks/config.yml Normal file
View file

@ -0,0 +1,2 @@
version: '3'

14
cmd/tasks/go.yml Normal file
View file

@ -0,0 +1,14 @@
version: '3'
tasks:
tidy:
desc: "Run go mod tidy in a specific directory."
dir: '{{.GO_DIR | default "."}}'
cmds:
- go mod tidy
build:
desc: "Run go build in a specific directory."
dir: '{{.GO_DIR | default "."}}'
cmds:
- go build -o {{.OUTPUT_PATH}} {{.BUILD_FLAGS}}

24
cmd/tasks/node.yml Normal file
View file

@ -0,0 +1,24 @@
version: '3'
tasks:
install:
desc: "Install npm dependencies for the frontend."
dir: '{{.PUBLIC_DIR}}'
sources:
- package.json
- package-lock.json
generates:
- node_modules/*
preconditions:
- sh: npm version
msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/"
cmds:
- npm install
build:
desc: "Build the frontend assets."
dir: '{{.PUBLIC_DIR}}'
deps:
- task: install
cmds:
- npm run build

21
cmd/tasks/wails.yml Normal file
View file

@ -0,0 +1,21 @@
version: '3'
tasks:
dev:
desc: "Runs the Wails dev server."
vars:
APP_ROOT: '{{.APP_ROOT | default "."}}'
VITE_PORT: '{{.VITE_PORT | default 9245}}'
cmds:
# Note the paths are now constructed from the passed-in APP_ROOT.
- wails3 dev -approot '{{.APP_ROOT}}' -config '{{.APP_ROOT}}/build/config.yml' -port {{.VITE_PORT}}
build:
desc: "Builds the Wails application."
vars:
APP_ROOT: '{{.APP_ROOT | default "."}}'
APP_NAME: '{{.APP_NAME | default "app"}}'
dir: '{{.APP_ROOT}}' # Run the build from the app's root directory.
cmds:
# This assumes you have a 'build' task in your platform-specific Taskfiles.
- wails3 build -platform '{{.GOOS}}/{{.GOARCH}}' -name '{{.APP_NAME}}'

View file

@ -1,6 +1,6 @@
go 1.25
use (
./pkg/v1/core
./pkg/core
./cmd/app
)

View file

@ -10,6 +10,8 @@ github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg=
github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=

2
pkg/app/.gitignore vendored
View file

@ -1,2 +0,0 @@
.task
/build/bin/core

11
pkg/core/actions.go Normal file
View file

@ -0,0 +1,11 @@
package core
import "github.com/wailsapp/wails/v3/pkg/application"
type ActionServiceStartup struct{}
// ActionDisplayOpenWindow is a structured message for requesting a new window.
type ActionDisplayOpenWindow struct {
Name string
Options application.WebviewWindowOptions
}

177
pkg/core/config/config.go Normal file
View file

@ -0,0 +1,177 @@
package config
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"github.com/Snider/Core"
"github.com/adrg/xdg"
)
const appName = "lethean"
const configFileName = "config.json"
// ErrSetupRequired is returned if the config file is missing and cannot be created.
var ErrSetupRequired = errors.New("setup required: config.json not found")
// Options holds configuration for the config service.
type Options struct{}
// Service provides access to the application's configuration.
// It handles loading, saving, and providing access to configuration values.
type Service struct {
*core.Runtime[Options] `json:"-"`
// Non-persistent fields, derived at runtime.
ConfigPath string `json:"-"`
UserHomeDir string `json:"-"`
RootDir string `json:"-"`
CacheDir string `json:"-"`
ConfigDir string `json:"-"`
DataDir string `json:"-"`
WorkspacesDir string `json:"-"`
// Persistent fields, saved to config.json.
DefaultRoute string `json:"default_route"`
Features []string `json:"features"`
Language string `json:"language"`
}
// New is a factory function that creates and initializes a new configuration service.
// It loads an existing configuration or creates a default one if not found.
func New(c *core.Core) (any, error) {
// --- Path and Directory Setup ---
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("could not resolve user home directory: %w", err)
}
userHomeDir := filepath.Join(homeDir, appName)
rootDir, err := xdg.DataFile(appName)
if err != nil {
return nil, fmt.Errorf("could not resolve data directory: %w", err)
}
cacheDir, err := xdg.CacheFile(appName)
if err != nil {
return nil, fmt.Errorf("could not resolve cache directory: %w", err)
}
s := &Service{
Runtime: core.NewRuntime(c, Options{}),
UserHomeDir: userHomeDir,
RootDir: rootDir,
CacheDir: cacheDir,
ConfigDir: filepath.Join(userHomeDir, "config"),
DataDir: filepath.Join(userHomeDir, "data"),
WorkspacesDir: filepath.Join(userHomeDir, "workspaces"),
DefaultRoute: "/",
Features: []string{},
Language: "en",
}
s.ConfigPath = filepath.Join(s.ConfigDir, configFileName)
dirs := []string{s.RootDir, s.ConfigDir, s.DataDir, s.CacheDir, s.WorkspacesDir, s.UserHomeDir}
for _, dir := range dirs {
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return nil, fmt.Errorf("could not create directory %s: %w", dir, err)
}
}
// --- Load or Create Configuration ---
if data, err := os.ReadFile(s.ConfigPath); err == nil {
// Config file exists, load it.
if err := json.Unmarshal(data, s); err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}
} else if os.IsNotExist(err) {
// Config file does not exist, create it with default values.
if err := s.Save(); err != nil {
return nil, fmt.Errorf("failed to create default config file: %w", err)
}
} else {
// Another error occurred reading the file.
return nil, fmt.Errorf("failed to read config file: %w", err)
}
c.RegisterAction(s.handleIPCEvents)
return s, nil
}
// handleIPCEvents is the central IPC handler for the config service.
func (s *Service) handleIPCEvents(c *core.Core, msg core.Message) error {
switch msg.(type) {
case core.ActionServiceStartup:
c.App.Logger.Info("Config service started")
default:
// No other actions are handled by this service yet.
}
return nil
}
// Save writes the current configuration to config.json.
func (s *Service) Save() error {
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
if err := os.WriteFile(s.ConfigPath, data, 0644); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}
// IsFeatureEnabled checks if a given feature is enabled in the configuration.
func (s *Service) IsFeatureEnabled(feature string) bool {
for _, f := range s.Features {
if f == feature {
return true
}
}
return false
}
// EnableFeature adds a feature to the list of enabled features and saves the config.
func (s *Service) EnableFeature(feature string) error {
if s.IsFeatureEnabled(feature) {
return nil
}
s.Features = append(s.Features, feature)
if err := s.Save(); err != nil {
return fmt.Errorf("failed to save config after enabling feature %s: %w", feature, err)
}
return nil
}
func (s *Service) Key(key string) (interface{}, error) {
// Use reflection to inspect the struct fields.
val := reflect.ValueOf(s).Elem()
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldName := field.Name
// Check the field name first.
if strings.EqualFold(fieldName, key) {
return val.Field(i).Interface(), nil
}
// Then check the `json` tag.
jsonTag := field.Tag.Get("json")
if jsonTag != "" && jsonTag != "-" {
jsonName := strings.Split(jsonTag, ",")[0]
if strings.EqualFold(jsonName, key) {
return val.Field(i).Interface(), nil
}
}
}
return nil, fmt.Errorf("key '%s' not found in config", key)
}

View file

@ -0,0 +1,146 @@
package config
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/Snider/Core"
)
// setupTestEnv creates a temporary home directory for testing and ensures a clean environment.
func setupTestEnv(t *testing.T) (string, func()) {
tempHomeDir, err := os.MkdirTemp("", "test_home_*")
if err != nil {
t.Fatalf("Failed to create temp home directory: %v", err)
}
oldHome := os.Getenv("HOME")
os.Setenv("HOME", tempHomeDir)
// Unset XDG vars to ensure HOME is used for path resolution, creating a hermetic test.
oldXdgData := os.Getenv("XDG_DATA_HOME")
oldXdgCache := os.Getenv("XDG_CACHE_HOME")
os.Unsetenv("XDG_DATA_HOME")
os.Unsetenv("XDG_CACHE_HOME")
cleanup := func() {
os.Setenv("HOME", oldHome)
os.Setenv("XDG_DATA_HOME", oldXdgData)
os.Setenv("XDG_CACHE_HOME", oldXdgCache)
os.RemoveAll(tempHomeDir)
}
return tempHomeDir, cleanup
}
// newTestCore creates a new, empty core instance for testing.
func newTestCore(t *testing.T) *core.Core {
c := core.New()
if c == nil {
t.Fatalf("core.New() returned a nil instance")
}
return c
}
func TestConfigService(t *testing.T) {
t.Run("New service creates default config", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
c := newTestCore(t)
serviceInstance, err := New(c)
if err != nil {
t.Fatalf("New() failed: %v", err)
}
s, ok := serviceInstance.(*Service)
if !ok {
t.Fatalf("Service instance is not of type *Service")
}
// Check that the config file was created
if _, err := os.Stat(s.ConfigPath); os.IsNotExist(err) {
t.Errorf("config.json was not created at %s", s.ConfigPath)
}
// Check default values
if s.Language != "en" {
t.Errorf("Expected default language 'en', got '%s'", s.Language)
}
})
t.Run("New service loads existing config", func(t *testing.T) {
tempHomeDir, cleanup := setupTestEnv(t)
defer cleanup()
// Manually create a config file with non-default values
configDir := filepath.Join(tempHomeDir, appName, "config")
if err := os.MkdirAll(configDir, os.ModePerm); err != nil {
t.Fatalf("Failed to create test config dir: %v", err)
}
configPath := filepath.Join(configDir, configFileName)
customConfig := `{"language": "fr", "features": ["beta-testing"]}`
if err := os.WriteFile(configPath, []byte(customConfig), 0644); err != nil {
t.Fatalf("Failed to write custom config file: %v", err)
}
c := newTestCore(t)
serviceInstance, err := New(c)
if err != nil {
t.Fatalf("New() failed while loading existing config: %v", err)
}
s, ok := serviceInstance.(*Service)
if !ok {
t.Fatalf("Service instance is not of type *Service")
}
if s.Language != "fr" {
t.Errorf("Expected language 'fr', got '%s'", s.Language)
}
if !s.IsFeatureEnabled("beta-testing") {
t.Errorf("Expected 'beta-testing' feature to be enabled")
}
})
t.Run("EnableFeature and Save", func(t *testing.T) {
_, cleanup := setupTestEnv(t)
defer cleanup()
c := newTestCore(t)
serviceInstance, err := New(c)
if err != nil {
t.Fatalf("New() failed: %v", err)
}
s, ok := serviceInstance.(*Service)
if !ok {
t.Fatalf("Service instance is not of type *Service")
}
if err := s.EnableFeature("new-feature"); err != nil {
t.Fatalf("EnableFeature() failed: %v", err)
}
data, err := os.ReadFile(s.ConfigPath)
if err != nil {
t.Fatalf("Failed to read config file: %v", err)
}
var onDiskService Service
if err := json.Unmarshal(data, &onDiskService); err != nil {
t.Fatalf("Failed to unmarshal saved config: %v", err)
}
found := false
for _, f := range onDiskService.Features {
if f == "new-feature" {
found = true
break
}
}
if !found {
t.Errorf("Enabled feature 'new-feature' was not saved to disk")
}
})
}

190
pkg/core/core.go Normal file
View file

@ -0,0 +1,190 @@
package core
import (
"context"
"embed"
"errors"
"fmt"
"reflect"
"strings"
"sync"
"github.com/wailsapp/wails/v3/pkg/application"
)
// --- Core Structs & Types ---
type Contract struct {
DontPanic bool
DisableLogging bool
}
type Option func(*Core) error
type Message interface{}
type Core struct {
once sync.Once
initErr error
App *application.App
assets embed.FS
serviceLock bool
ipcMu sync.RWMutex
ipcHandlers []func(*Core, Message) error
serviceMu sync.RWMutex
services map[string]any
servicesLocked bool
}
var instance *Core
// New initialises a Core instance using the provided options and performs the necessary setup.
func New(opts ...Option) *Core {
c := &Core{
services: make(map[string]any),
}
for _, o := range opts {
if err := o(c); err != nil {
return nil
}
}
c.once.Do(func() {
c.initErr = nil
})
if c.initErr != nil {
return nil
}
if c.serviceLock {
c.servicesLocked = true
}
return c
}
// WithService creates an Option that registers a service. It automatically discovers
// the service name from its package path and registers its IPC handler if it
// implements a method named `HandleIPCEvents`.
func WithService(factory func(*Core) (any, error)) Option {
return func(c *Core) error {
serviceInstance, err := factory(c)
if err != nil {
return fmt.Errorf("core: failed to create service: %w", err)
}
// --- Service Name Discovery ---
typeOfService := reflect.TypeOf(serviceInstance)
if typeOfService.Kind() == reflect.Ptr {
typeOfService = typeOfService.Elem()
}
pkgPath := typeOfService.PkgPath()
parts := strings.Split(pkgPath, "/")
name := parts[len(parts)-1]
// --- IPC Handler Discovery ---
instanceValue := reflect.ValueOf(serviceInstance)
handlerMethod := instanceValue.MethodByName("HandleIPCEvents")
if handlerMethod.IsValid() {
if handler, ok := handlerMethod.Interface().(func(*Core, Message) error); ok {
c.RegisterAction(handler)
}
}
return c.RegisterService(name, serviceInstance)
}
}
func WithWails(app *application.App) Option {
return func(c *Core) error {
c.App = app
return nil
}
}
func WithAssets(fs embed.FS) Option {
return func(c *Core) error {
c.assets = fs
return nil
}
}
func WithServiceLock() Option {
return func(c *Core) error {
c.serviceLock = true
return nil
}
}
// --- Core Methods ---
func (c *Core) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
return c.ACTION(ActionServiceStartup{})
}
func (c *Core) ACTION(msg Message) error {
c.ipcMu.RLock()
handlers := append([]func(*Core, Message) error(nil), c.ipcHandlers...)
c.ipcMu.RUnlock()
var agg error
for _, h := range handlers {
if err := h(c, msg); err != nil {
agg = fmt.Errorf("%w; %v", agg, err)
}
}
return agg
}
func (c *Core) RegisterAction(handler func(*Core, Message) error) {
c.ipcMu.Lock()
c.ipcHandlers = append(c.ipcHandlers, handler)
c.ipcMu.Unlock()
}
func (c *Core) RegisterActions(handlers ...func(*Core, Message) error) {
c.ipcMu.Lock()
c.ipcHandlers = append(c.ipcHandlers, handlers...)
c.ipcMu.Unlock()
}
func (c *Core) RegisterService(name string, api any) error {
if c.servicesLocked {
return fmt.Errorf("core: service %q is not permitted by the serviceLock setting", name)
}
if name == "" {
return errors.New("core: service name cannot be empty")
}
c.serviceMu.Lock()
defer c.serviceMu.Unlock()
if _, exists := c.services[name]; exists {
return fmt.Errorf("core: service %q already registered", name)
}
c.services[name] = api
return nil
}
func (c *Core) Service(name string) any {
c.serviceMu.RLock()
api, ok := c.services[name]
c.serviceMu.RUnlock()
if !ok {
return nil
}
return api
}
func ServiceFor[T any](c *Core, name string) *T {
raw := c.Service(name)
typed, ok := raw.(*T)
if !ok {
return nil
}
return typed
}
// App returns the global application instance.
func App() *application.App {
app := ServiceFor[application.App](instance, "App")
if instance == nil || app == nil {
panic("core.App() called before core.Setup() was successfully initialized")
}
return app
}
func (c *Core) Core() *Core { return c }

76
pkg/core/crypt/crypt.go Normal file
View file

@ -0,0 +1,76 @@
package crypt
import (
"context"
"fmt"
"io"
"github.com/Snider/Core"
"github.com/Snider/Core/crypt/openpgp"
"github.com/wailsapp/wails/v3/pkg/application"
)
// Options holds configuration for the crypt service.
type Options struct{}
// Service provides cryptographic functions to the application.
type Service struct {
*core.Runtime[Options]
}
// HashType defines the supported hashing algorithms.
type HashType string
const (
LTHN HashType = "lthn"
SHA512 HashType = "sha512"
SHA256 HashType = "sha256"
SHA1 HashType = "sha1"
MD5 HashType = "md5"
)
// Service provides cryptographic functions.
// It is the main entry point for all cryptographic operations
// and is bound to the frontend.
type API struct {
core *core.Core
}
// New is a factory function that creates a new crypt Service.
func New(c *core.Core) (any, error) {
s := &Service{
Runtime: core.NewRuntime(c, Options{}),
}
return s, nil
}
// handleIPCEvents is the central IPC handler for the crypt service.
func (s *Service) handleIPCEvents(c *core.Core, msg core.Message) error {
switch msg.(type) {
case core.ActionServiceStartup:
return s.ServiceStartup(context.Background(), application.ServiceOptions{})
default:
c.App.Logger.Error("Crypt: Unknown message type", "type", fmt.Sprintf("%T", msg))
}
return nil
}
// ServiceStartup is called when the app starts. It handles one-time cryptographic setup.
func (s *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
s.Core().App.Logger.Info("Crypt service started")
// Key generation logic will be implemented here, likely depending on the config service.
return nil
}
// EncryptPGP encrypts data for a recipient, optionally signing it.
// It acts as a wrapper around the underlying openpgp library function.
func (s *Service) EncryptPGP(writer io.Writer, recipientPath, data string, signerPath, signerPassphrase *string) (string, error) {
return openpgp.EncryptPGP(writer, recipientPath, data, signerPath, signerPassphrase)
}
// DecryptPGP decrypts a PGP message, optionally verifying the signature.
// It acts as a wrapper around the underlying openpgp library function.
func (s *Service) DecryptPGP(recipientPath, message, passphrase string, signerPath *string) (string, error) {
return openpgp.DecryptPGP(recipientPath, message, passphrase, signerPath)
}

View file

@ -7,7 +7,7 @@ import (
"crypto/sha512"
"encoding/hex"
"github.com/Snider/Core/crypt/lib/lthn"
"github.com/Snider/Core/crypt/lthn"
)
// Hash computes a hash of the payload using the specified algorithm.

View file

@ -5,6 +5,21 @@ import (
"encoding/hex"
)
// keyMap is the default character-swapping map used for the quasi-salting process.
var keyMap = map[rune]rune{
'o': '0',
'l': '1',
'e': '3',
'a': '4',
's': 'z',
't': '7',
'0': 'o',
'1': 'l',
'3': 'e',
'4': 'a',
'7': 't',
}
// SetKeyMap sets the key map for the notarisation process.
func SetKeyMap(newKeyMap map[rune]rune) {
keyMap = newKeyMap

View file

@ -8,19 +8,18 @@ import (
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/Snider/Core/io"
)
// EncryptPGP encrypts data for a recipient, optionally signing it.
func EncryptPGP(medium io.Medium, recipientPath, data string, signerPath, signerPassphrase *string) (string, error) {
recipient, err := GetPublicKey(medium, recipientPath)
func EncryptPGP(writer io.Writer, recipientPath, data string, signerPath, signerPassphrase *string) (string, error) {
recipient, err := GetPublicKey(recipientPath)
if err != nil {
return "", fmt.Errorf("failed to get recipient public key: %w", err)
}
var signer *openpgp.Entity
if signerPath != nil && signerPassphrase != nil {
signer, err = GetPrivateKey(medium, *signerPath, *signerPassphrase)
signer, err = GetPrivateKey(*signerPath, *signerPassphrase)
if err != nil {
return "", fmt.Errorf("could not get private key for signing: %w", err)
}
@ -55,8 +54,8 @@ func EncryptPGP(medium io.Medium, recipientPath, data string, signerPath, signer
}
// DecryptPGP decrypts a PGP message, optionally verifying the signature.
func DecryptPGP(medium io.Medium, recipientPath, message, passphrase string, signerPath *string) (string, error) {
privateKeyEntity, err := GetPrivateKey(medium, recipientPath, passphrase)
func DecryptPGP(recipientPath, message, passphrase string, signerPath *string) (string, error) {
privateKeyEntity, err := GetPrivateKey(recipientPath, passphrase)
if err != nil {
return "", fmt.Errorf("failed to get private key: %w", err)
}
@ -66,7 +65,7 @@ func DecryptPGP(medium io.Medium, recipientPath, message, passphrase string, sig
var expectedSigner *openpgp.Entity
if signerPath != nil {
publicKeyEntity, err := GetPublicKey(medium, *signerPath)
publicKeyEntity, err := GetPublicKey(*signerPath)
if err != nil {
return "", fmt.Errorf("could not get public key for verification: %w", err)
}

View file

@ -0,0 +1,155 @@
package openpgp
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
)
// generateTestKeys creates a new PGP entity and saves the public and private keys to temporary files.
func generateTestKeys(t *testing.T, name, passphrase string) (string, string, func()) {
t.Helper()
tempDir, err := os.MkdirTemp("", "pgp-keys-*")
if err != nil {
t.Fatalf("Failed to create temp dir for keys: %v", err)
}
entity, err := openpgp.NewEntity(name, "", name, nil)
if err != nil {
t.Fatalf("Failed to create new PGP entity: %v", err)
}
// Encrypt the private key with the passphrase
if err := entity.PrivateKey.Encrypt([]byte(passphrase)); err != nil {
t.Fatalf("Failed to encrypt private key: %v", err)
}
// --- Save Public Key ---
pubKeyPath := filepath.Join(tempDir, name+".pub")
pubKeyFile, err := os.Create(pubKeyPath)
if err != nil {
t.Fatalf("Failed to create public key file: %v", err)
}
w, err := armor.Encode(pubKeyFile, openpgp.PublicKeyType, nil)
if err != nil {
t.Fatalf("Failed to create armored writer for public key: %v", err)
}
if err := entity.Serialize(w); err != nil {
t.Fatalf("Failed to serialize public key: %v", err)
}
w.Close()
pubKeyFile.Close()
// --- Save Private Key ---
privKeyPath := filepath.Join(tempDir, name+".asc")
privKeyFile, err := os.Create(privKeyPath)
if err != nil {
t.Fatalf("Failed to create private key file: %v", err)
}
w, err = armor.Encode(privKeyFile, openpgp.PrivateKeyType, nil)
if err != nil {
t.Fatalf("Failed to create armored writer for private key: %v", err)
}
if err := entity.SerializePrivate(w, nil); err != nil {
t.Fatalf("Failed to serialize private key: %v", err)
}
w.Close()
privKeyFile.Close()
cleanup := func() { os.RemoveAll(tempDir) }
return pubKeyPath, privKeyPath, cleanup
}
func TestEncryptDecryptPGP(t *testing.T) {
recipientPub, recipientPriv, cleanup := generateTestKeys(t, "recipient", "recipient-pass")
defer cleanup()
originalMessage := "This is a secret message."
// --- Test Encryption ---
var encryptedBuf bytes.Buffer
encryptedMessage, err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, nil, nil)
if err != nil {
t.Fatalf("EncryptPGP() failed: %v", err)
}
if !strings.Contains(encryptedMessage, "-----BEGIN PGP MESSAGE-----") {
t.Errorf("Encrypted message does not appear to be PGP armored")
}
// --- Test Decryption ---
decryptedMessage, err := DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", nil)
if err != nil {
t.Fatalf("DecryptPGP() failed: %v", err)
}
if decryptedMessage != originalMessage {
t.Errorf("Decrypted message does not match original. got=%q, want=%q", decryptedMessage, originalMessage)
}
}
func TestSignAndVerifyPGP(t *testing.T) {
recipientPub, recipientPriv, rCleanup := generateTestKeys(t, "recipient", "recipient-pass")
defer rCleanup()
signerPub, signerPriv, sCleanup := generateTestKeys(t, "signer", "signer-pass")
defer sCleanup()
originalMessage := "This is a signed and verified message."
// --- Encrypt and Sign ---
var encryptedBuf bytes.Buffer
signerPass := "signer-pass"
encryptedMessage, err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, &signerPriv, &signerPass)
if err != nil {
t.Fatalf("EncryptPGP() with signing failed: %v", err)
}
// --- Decrypt and Verify ---
decryptedMessage, err := DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", &signerPub)
if err != nil {
t.Fatalf("DecryptPGP() with verification failed: %v", err)
}
if decryptedMessage != originalMessage {
t.Errorf("Decrypted message does not match original. got=%q, want=%q", decryptedMessage, originalMessage)
}
}
func TestVerificationFailure(t *testing.T) {
recipientPub, recipientPriv, rCleanup := generateTestKeys(t, "recipient", "recipient-pass")
defer rCleanup()
_, signerPriv, sCleanup := generateTestKeys(t, "signer", "signer-pass")
defer sCleanup()
// Generate a third, unexpected key to test verification failure
unexpectedSignerPub, _, uCleanup := generateTestKeys(t, "unexpected", "unexpected-pass")
defer uCleanup()
originalMessage := "This message should fail verification."
// --- Encrypt and Sign with the actual signer key ---
var encryptedBuf bytes.Buffer
signerPass := "signer-pass"
encryptedMessage, err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, &signerPriv, &signerPass)
if err != nil {
t.Fatalf("EncryptPGP() with signing failed: %v", err)
}
// --- Attempt to Decrypt and Verify with the WRONG public key ---
_, err = DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", &unexpectedSignerPub)
if err == nil {
t.Fatal("DecryptPGP() did not fail, but verification with an incorrect key was expected to fail.")
}
if !strings.Contains(err.Error(), "signature from unexpected key") {
t.Errorf("Expected error to contain 'signature from unexpected key', but got: %v", err)
}
}

View file

@ -5,14 +5,12 @@ import (
"crypto"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/armor"
"github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/Snider/Core/crypt/lib/lthn"
"github.com/Snider/Core/io"
"github.com/Snider/Core/crypt/lthn"
)
// CreateKeyPair generates a new OpenPGP key pair.
@ -72,13 +70,13 @@ func CreateServerKeyPair(keysDir string) error {
}
// GetPublicKey retrieves an armored public key for a given ID.
func GetPublicKey(medium io.Medium, path string) (*openpgp.Entity, error) {
return readEntity(medium, path)
func GetPublicKey(path string) (*openpgp.Entity, error) {
return readEntity(path)
}
// GetPrivateKey retrieves and decrypts an armored private key.
func GetPrivateKey(medium io.Medium, path, passphrase string) (*openpgp.Entity, error) {
entity, err := readEntity(medium, path)
func GetPrivateKey(path, passphrase string) (*openpgp.Entity, error) {
entity, err := readEntity(path)
if err != nil {
return nil, err
}
@ -123,54 +121,55 @@ func GetPrivateKey(medium io.Medium, path, passphrase string) (*openpgp.Entity,
// --- Helper Functions ---
func createAndStoreKeyPair(id, password, dir string) error {
var keyPair *KeyPair
//var keyPair *KeyPair
var err error
if password != "" {
keyPair, err = CreateKeyPair(id, password)
} else {
keyPair, err = CreateKeyPair(id)
}
//if password != "" {
// keyPair, err = CreateKeyPair(id, password)
//} else {
// keyPair, err = CreateKeyPair(id)
//}
if err != nil {
return fmt.Errorf("failed to create key pair for id %s: %w", id, err)
}
if err := io.Local.EnsureDir(dir); err != nil {
return fmt.Errorf("failed to ensure key directory exists: %w", err)
}
files := map[string]string{
filepath.Join(dir, fmt.Sprintf("%s.lthn.pub", id)): keyPair.PublicKey,
filepath.Join(dir, fmt.Sprintf("%s.lthn.key", id)): keyPair.PrivateKey,
filepath.Join(dir, fmt.Sprintf("%s.lthn.rev", id)): keyPair.RevocationCertificate, // Re-enabled
}
for path, content := range files {
if content == "" {
continue
}
if err := io.Local.Write(path, content); err != nil {
return fmt.Errorf("failed to write key file %s: %w", path, err)
}
}
//if err := io.Local.EnsureDir(dir); err != nil {
// return fmt.Errorf("failed to ensure key directory exists: %w", err)
//}
//
//files := map[string]string{
// filepath.Join(dir, fmt.Sprintf("%s.lthn.pub", id)): keyPair.PublicKey,
// filepath.Join(dir, fmt.Sprintf("%s.lthn.key", id)): keyPair.PrivateKey,
// filepath.Join(dir, fmt.Sprintf("%s.lthn.rev", id)): keyPair.RevocationCertificate, // Re-enabled
//}
//
//for path, content := range files {
// if content == "" {
// continue
// }
// if err := io.Local.Write(path, content); err != nil {
// return fmt.Errorf("failed to write key file %s: %w", path, err)
// }
//}
return nil
}
func readEntity(m io.Medium, path string) (*openpgp.Entity, error) {
keyArmored, err := m.Read(path)
if err != nil {
return nil, fmt.Errorf("failed to read key file %s: %w", path, err)
}
func readEntity(path string) (*openpgp.Entity, error) {
//keyArmored, err := m.Read(path)
//if err != nil {
// return nil, fmt.Errorf("failed to read key file %s: %w", path, err)
//}
entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(keyArmored))
if err != nil {
return nil, fmt.Errorf("failed to parse key file %s: %w", path, err)
}
if len(entityList) == 0 {
return nil, fmt.Errorf("no entity found in key file %s", path)
}
return entityList[0], nil
//entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(keyArmored))
//if err != nil {
// return nil, fmt.Errorf("failed to parse key file %s: %w", path, err)
//}
//if len(entityList) == 0 {
// return nil, fmt.Errorf("no entity found in key file %s", path)
//}
//return entityList[0], nil
return nil, nil
}
func serializeEntity(entity *openpgp.Entity, keyType string, password string) (string, error) {

View file

@ -6,12 +6,11 @@ import (
"strings"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/Snider/Core/io"
)
// Sign creates a detached signature for the data.
func Sign(medium io.Medium, data, privateKeyPath, passphrase string) (string, error) {
signer, err := GetPrivateKey(medium, privateKeyPath, passphrase)
func Sign(data, privateKeyPath, passphrase string) (string, error) {
signer, err := GetPrivateKey(privateKeyPath, passphrase)
if err != nil {
return "", fmt.Errorf("failed to get private key for signing: %w", err)
}
@ -25,8 +24,8 @@ func Sign(medium io.Medium, data, privateKeyPath, passphrase string) (string, er
}
// Verify checks a detached signature.
func Verify(medium io.Medium, data, signature, publicKeyPath string) (bool, error) {
keyring, err := GetPublicKey(medium, publicKeyPath)
func Verify(data, signature, publicKeyPath string) (bool, error) {
keyring, err := GetPublicKey(publicKeyPath)
if err != nil {
return false, fmt.Errorf("failed to get public key for verification: %w", err)
}

View file

@ -0,0 +1,8 @@
package display
import "github.com/wailsapp/wails/v3/pkg/application"
// ActionOpenWindow is an IPC message used to request a new window.
type ActionOpenWindow struct {
application.WebviewWindowOptions
}

127
pkg/core/display/display.go Normal file
View file

@ -0,0 +1,127 @@
package display
import (
"context"
"fmt"
"github.com/Snider/Core"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
// Options holds configuration for the display service.
type Options struct{}
// Service manages windowing, dialogs, and other visual elements.
type Service struct {
*core.Runtime[Options]
}
// New is a factory function that creates a new display Service.
func New(c *core.Core) (any, error) {
return &Service{
Runtime: core.NewRuntime(c, Options{}),
}, nil
}
func (s *Service) ServiceName() string { return "github.com/Snider/Core/display" }
// HandleIPCEvents processes IPC messages and performs actions such as opening windows or initializing services based on message types.
func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error {
switch m := msg.(type) {
case map[string]any:
if action, ok := m["action"].(string); ok && action == "display.open_window" {
return s.handleOpenWindowAction(m)
}
case ActionOpenWindow:
_, err := s.NewWithStruct(&m.WebviewWindowOptions)
return err
case core.ActionServiceStartup:
return s.ServiceStartup(context.Background(), application.ServiceOptions{})
default:
c.App.Logger.Error("Display: Unknown message type", "type", fmt.Sprintf("%T", m))
}
return nil
}
// handleOpenWindowAction processes a message to configure and create a new window using specified name and options.
func (s *Service) handleOpenWindowAction(msg map[string]any) error {
opts := application.WebviewWindowOptions{}
if name, ok := msg["name"].(string); ok {
opts.Name = name
}
if optsMap, ok := msg["options"].(map[string]any); ok {
if title, ok := optsMap["Title"].(string); ok {
opts.Title = title
}
if width, ok := optsMap["Width"].(float64); ok {
opts.Width = int(width)
}
if height, ok := optsMap["Height"].(float64); ok {
opts.Height = int(height)
}
}
s.Core().App.Window.NewWithOptions(opts)
return nil
}
// ShowEnvironmentDialog displays a dialog containing detailed information about the application's runtime environment.
func (s *Service) ShowEnvironmentDialog() {
envInfo := s.Core().App.Env.Info()
details := fmt.Sprintf(`Environment Information:
Operating System: %s
Architecture: %s
Debug Mode: %t
Dark Mode: %t
Platform Information:`,
envInfo.OS,
envInfo.Arch,
envInfo.Debug,
s.Core().App.Env.IsDarkMode()) // Use d.core.App
// Add platform-specific details
for key, value := range envInfo.PlatformInfo {
details += fmt.Sprintf("\n%s: %v", key, value)
}
if envInfo.OSInfo != nil {
details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s",
envInfo.OSInfo.Name,
envInfo.OSInfo.Version)
}
dialog := s.Core().App.Dialog.Info()
dialog.SetTitle("Environment Information")
dialog.SetMessage(details)
dialog.Show()
}
// ServiceStartup initializes the display service and sets up the main application window and system tray.
func (s *Service) ServiceStartup(context.Context, application.ServiceOptions) error {
s.Core().App.Logger.Info("Display service started")
s.buildMenu()
s.systemTray()
// This will be updated to use the restored OpenWindow method
mainOpts := application.WebviewWindowOptions{
Name: "main",
Title: "Core",
Height: 900,
Width: 1280,
URL: "/",
}
s.Core().App.Window.NewWithOptions(mainOpts)
return nil
}
// monitorScreenChanges listens for theme change events and logs when screen configuration changes occur.
func (s *Service) monitorScreenChanges() {
s.Core().App.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
s.Core().App.Logger.Info("Screen configuration changed")
})
}

View file

@ -7,8 +7,8 @@ import (
)
// buildMenu creates and sets the main application menu.
func (d *API) buildMenu() {
appMenu := d.core.App.Menu.New()
func (s *Service) buildMenu() {
appMenu := s.Core().App.Menu.New()
if runtime.GOOS == "darwin" {
appMenu.AddRole(application.AppMenu)
}
@ -28,5 +28,5 @@ func (d *API) buildMenu() {
appMenu.AddRole(application.WindowMenu)
appMenu.AddRole(application.HelpMenu)
d.core.App.Menu.Set(appMenu)
s.Core().App.Menu.Set(appMenu)
}

View file

@ -7,9 +7,9 @@ import (
)
// setupTray configures and creates the system tray icon and menu.
func (d *API) systemTray() {
func (s *Service) systemTray() {
systray := d.core.App.SystemTray.New()
systray := s.Core().App.SystemTray.New()
systray.SetTooltip("Core")
systray.SetLabel("Core")
//appTrayIcon, _ := d.assets.ReadFile("assets/apptray.png")
@ -22,7 +22,7 @@ func (d *API) systemTray() {
// systray.SetIcon(appTrayIcon)
//}
// Create a hidden window for the system tray menu to interact with
trayWindow := d.NewWithStruct(&Window{
trayWindow, _ := s.NewWithStruct(&Window{
Name: "system-tray",
Title: "System Tray Status",
URL: "system-tray.html",
@ -33,20 +33,20 @@ func (d *API) systemTray() {
systray.AttachWindow(trayWindow).WindowOffset(5)
// --- Build Tray Menu ---
trayMenu := d.core.App.Menu.New()
trayMenu := s.Core().App.Menu.New()
trayMenu.Add("Open Desktop").OnClick(func(ctx *application.Context) {
for _, window := range d.core.App.Window.GetAll() {
for _, window := range s.Core().App.Window.GetAll() {
window.Show()
}
})
trayMenu.Add("Close Desktop").OnClick(func(ctx *application.Context) {
for _, window := range d.core.App.Window.GetAll() {
for _, window := range s.Core().App.Window.GetAll() {
window.Hide()
}
})
trayMenu.Add("Environment Info").OnClick(func(ctx *application.Context) {
d.ShowEnvironmentDialog()
s.ShowEnvironmentDialog()
})
// Add brand-specific menu items
//switch d.brand {
@ -65,7 +65,7 @@ func (d *API) systemTray() {
trayMenu.AddSeparator()
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
d.core.App.Quit()
s.Core().App.Quit()
})
systray.SetMenu(trayMenu)

View file

@ -0,0 +1,93 @@
package display
import (
"github.com/wailsapp/wails/v3/pkg/application"
)
type WindowOption func(*application.WebviewWindowOptions) error
type Window = application.WebviewWindowOptions
func WindowName(s string) WindowOption {
return func(o *Window) error {
o.Name = s
return nil
}
}
func WindowTitle(s string) WindowOption {
return func(o *Window) error {
o.Title = s
return nil
}
}
func WindowURL(s string) WindowOption {
return func(o *Window) error {
o.URL = s
return nil
}
}
func WindowWidth(i int) WindowOption {
return func(o *Window) error {
o.Width = i
return nil
}
}
func WindowHeight(i int) WindowOption {
return func(o *Window) error {
o.Height = i
return nil
}
}
func applyOptions(opts ...WindowOption) *Window {
w := &Window{}
if opts == nil {
return w
}
for _, o := range opts {
if err := o(w); err != nil {
return nil
}
}
return w
}
// NewWithStruct creates a new window using the provided options and returns its handle.
func (s *Service) NewWithStruct(options *Window) (*application.WebviewWindow, error) {
return s.Core().App.Window.NewWithOptions(*options), nil
}
// NewWithOptions creates a new window by applying a series of options.
func (s *Service) NewWithOptions(opts ...WindowOption) (*application.WebviewWindow, error) {
return s.NewWithStruct(applyOptions(opts...))
}
// NewWithURL creates a new default window pointing to the specified URL.
func (s *Service) NewWithURL(url string) (*application.WebviewWindow, error) {
return s.NewWithOptions(
WindowURL(url),
WindowTitle("Core"),
WindowHeight(900),
WindowWidth(1280),
)
}
// OpenWindow is a convenience method that creates and shows a window from a set of options.
func (s *Service) OpenWindow(opts ...WindowOption) error {
_, err := s.NewWithOptions(opts...)
return err
}
// SelectDirectory opens a directory selection dialog and returns the selected path.
func (s *Service) SelectDirectory() (string, error) {
dialog := application.OpenFileDialog()
dialog.SetTitle("Select Project Directory")
return dialog.PromptForSingleSelection()
}
var instance *Window
func (s *Service) Window() *Window { return instance }

View file

@ -1 +0,0 @@
.cache

50
pkg/core/docs/docs.go Normal file
View file

@ -0,0 +1,50 @@
package docs
import (
"embed"
"github.com/Snider/Core"
)
//go:embed all:static/*
var docsStatic embed.FS
// Options holds configuration for the doc service.
type Options struct{}
// Service manages the documentation assets and display requests.
// It embeds the core.Runtime to get access to the core instance and its functions.
type Service struct {
*core.Runtime[Options]
assets embed.FS
}
// New is a factory function that creates a new docs Service.
// It is self-contained and only depends on the Core, with no knowledge
// of other services at compile time.
func New(c *core.Core) (any, error) {
s := &Service{
Runtime: core.NewRuntime(c, Options{}),
assets: docsStatic,
}
return s, nil
}
// Show triggers the display of the documentation window.
func (s *Service) Show() error {
// The message is a generic map, which any service can create. The 'display'
// service will register a handler that knows how to interpret this structure.
msg := map[string]any{
"action": "display.open_window",
"name": "docs",
"options": map[string]any{
"Title": "Documentation",
"Width": 800,
"Height": 600,
},
}
// Dispatch the message through the core. The core will route it to the
// appropriate handler, in this case, the one registered by the display service.
return s.Core().ACTION(msg)
}

View file

@ -16,7 +16,7 @@
<title>Core Documentation</title>
<title>Core.Help</title>
@ -31,13 +31,15 @@
<script src="/assets/external/unpkg.com/iframe-worker/shim.js"></script>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<link rel="stylesheet" href="/assets/external/fonts.googleapis.com/css.49ea35f2.css">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
@ -79,7 +81,7 @@
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="/." title="Core Documentation" class="md-header__button md-logo" aria-label="Core Documentation" data-md-component="logo">
<a href="/index.html" title="Core.Help" class="md-header__button md-logo" aria-label="Core.Help" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
@ -93,7 +95,7 @@
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Core Documentation
Core.Help
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
@ -217,13 +219,146 @@
<li class="md-tabs__item">
<a href="/." class="md-tabs__link">
<a href="/index.html" class="md-tabs__link">
Core.Help
Overview
</a>
</li>
<li class="md-tabs__item">
<a href="/core/index.html" class="md-tabs__link">
Core
</a>
</li>
<li class="md-tabs__item">
<a href="/core/config.html" class="md-tabs__link">
Config
</a>
</li>
<li class="md-tabs__item">
<a href="/core/crypt.html" class="md-tabs__link">
Crypt
</a>
</li>
<li class="md-tabs__item">
<a href="/core/display.html" class="md-tabs__link">
Display
</a>
</li>
<li class="md-tabs__item">
<a href="/core/docs.html" class="md-tabs__link">
Docs
</a>
</li>
<li class="md-tabs__item">
<a href="/core/io.html" class="md-tabs__link">
IO
</a>
</li>
<li class="md-tabs__item">
<a href="/core/workspace.html" class="md-tabs__link">
Workspace
</a>
</li>
@ -252,13 +387,13 @@
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="/." title="Core Documentation" class="md-nav__button md-logo" aria-label="Core Documentation" data-md-component="logo">
<a href="/index.html" title="Core.Help" class="md-nav__button md-logo" aria-label="Core.Help" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Core Documentation
Core.Help
</label>
<div class="md-nav__source">
@ -282,12 +417,166 @@
<li class="md-nav__item">
<a href="/." class="md-nav__link">
<a href="/index.html" class="md-nav__link">
<span class="md-ellipsis">
Core.Help
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/core/index.html" class="md-nav__link">
<span class="md-ellipsis">
Core
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/core/config.html" class="md-nav__link">
<span class="md-ellipsis">
Config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/core/crypt.html" class="md-nav__link">
<span class="md-ellipsis">
Crypt
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/core/display.html" class="md-nav__link">
<span class="md-ellipsis">
Display
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/core/docs.html" class="md-nav__link">
<span class="md-ellipsis">
Docs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/core/io.html" class="md-nav__link">
<span class="md-ellipsis">
IO
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/core/workspace.html" class="md-nav__link">
<span class="md-ellipsis">
Workspace
</span>

View file

@ -0,0 +1,756 @@
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkC3kaWzU.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkAnkaWzU.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkCnkaWzU.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkBXkaWzU.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* math */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkenkaWzU.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkaHkaWzU.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkCXkaWzU.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkCHkaWzU.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkBnka.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkC3kaWzU.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkAnkaWzU.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkCnkaWzU.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkBXkaWzU.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* math */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkenkaWzU.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkaHkaWzU.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkCXkaWzU.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkCHkaWzU.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkBnka.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkC3kaWzU.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkAnkaWzU.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkCnkaWzU.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkBXkaWzU.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* math */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkenkaWzU.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkaHkaWzU.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkCXkaWzU.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkCHkaWzU.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: italic;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO5CnqEu92Fr1Mu53ZEC9_Vu3r1gIhOszmkBnka.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBGEe.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3iUBGEe.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3CUBGEe.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3-UBGEe.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* math */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMawCUBGEe.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMaxKUBGEe.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3OUBGEe.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3KUBGEe.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBA.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBGEe.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3iUBGEe.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3CUBGEe.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3-UBGEe.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* math */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMawCUBGEe.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMaxKUBGEe.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3OUBGEe.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3KUBGEe.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBA.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBGEe.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3iUBGEe.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3CUBGEe.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3-UBGEe.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* math */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMawCUBGEe.woff2) format('woff2');
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMaxKUBGEe.woff2) format('woff2');
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3OUBGEe.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3KUBGEe.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-stretch: 100%;
font-display: fallback;
src: url(../fonts.gstatic.com/s/roboto/v49/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBA.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3CWWoKC.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3mWWoKC.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm36WWoKC.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3KWWoKC.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3OWWoKC.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm32WWg.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3CWWoKC.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3mWWoKC.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm36WWoKC.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3KWWoKC.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm3OWWoKC.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto Mono';
font-style: italic;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x7DF4xlVMF-BfR8bXMIjhOm32WWg.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhGq3-OXg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhPq3-OXg.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhIq3-OXg.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhEq3-OXg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhFq3-OXg.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhLq38.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhGq3-OXg.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhPq3-OXg.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhIq3-OXg.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhEq3-OXg.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhFq3-OXg.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 700;
font-display: fallback;
src: url(../fonts.gstatic.com/s/robotomono/v31/L0x5DF4xlVMF-BfR8bXMIjhLq38.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

View file

@ -0,0 +1 @@
"use strict";(()=>{function c(s,n){parent.postMessage(s,n||"*")}function d(...s){return s.reduce((n,e)=>n.then(()=>new Promise(r=>{let t=document.createElement("script");t.src=e,t.onload=r,document.body.appendChild(t)})),Promise.resolve())}var o=class extends EventTarget{constructor(e){super();this.url=e;this.m=e=>{e.source===this.w&&(this.dispatchEvent(new MessageEvent("message",{data:e.data})),this.onmessage&&this.onmessage(e))};this.e=(e,r,t,i,m)=>{if(r===`${this.url}`){let a=new ErrorEvent("error",{message:e,filename:r,lineno:t,colno:i,error:m});this.dispatchEvent(a),this.onerror&&this.onerror(a)}};let r=document.createElement("iframe");r.hidden=!0,document.body.appendChild(this.iframe=r),this.w.document.open(),this.w.document.write(`<html><body><script>postMessage=${c};importScripts=${d};addEventListener("error",ev=>{parent.dispatchEvent(new ErrorEvent("error",{filename:"${e}",error:ev.error}))})<\/script><script src=${e}?${+Date.now()}><\/script></body></html>`),this.w.document.close(),onmessage=this.m,onerror=this.e,this.r=new Promise((t,i)=>{this.w.onload=t,this.w.onerror=i})}terminate(){document.body.removeChild(this.iframe),onmessage=onerror=null}postMessage(e){this.r.catch().then(()=>{this.w.dispatchEvent(new MessageEvent("message",{data:e}))})}get w(){return this.iframe.contentWindow}};window.IFrameWorker=o;location.protocol==="file:"&&(window.Worker=o);})();

File diff suppressed because one or more lines are too long

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,881 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="canonical" href="https://dappco.re/core/config.html">
<link rel="prev" href="index.html">
<link rel="next" href="crypt.html">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.22">
<title>Core.Config - Core.Help</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
<script src="../assets/external/unpkg.com/iframe-worker/shim.js"></script>
<link rel="stylesheet" href="../assets/external/fonts.googleapis.com/css.49ea35f2.css">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../assets/stylesheets/extra.css">
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="blue" data-md-color-accent="blue">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#coreconfig" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../index.html" title="Core.Help" class="md-header__button md-logo" aria-label="Core.Help" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Core.Help
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Core.Config
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="blue" data-md-color-accent="blue" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="blue" data-md-color-accent="blue" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/Snider/Core" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../index.html" class="md-tabs__link">
Overview
</a>
</li>
<li class="md-tabs__item">
<a href="index.html" class="md-tabs__link">
Core
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="config.html" class="md-tabs__link">
Config
</a>
</li>
<li class="md-tabs__item">
<a href="crypt.html" class="md-tabs__link">
Crypt
</a>
</li>
<li class="md-tabs__item">
<a href="display.html" class="md-tabs__link">
Display
</a>
</li>
<li class="md-tabs__item">
<a href="docs.html" class="md-tabs__link">
Docs
</a>
</li>
<li class="md-tabs__item">
<a href="io.html" class="md-tabs__link">
IO
</a>
</li>
<li class="md-tabs__item">
<a href="workspace.html" class="md-tabs__link">
Workspace
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../index.html" title="Core.Help" class="md-nav__button md-logo" aria-label="Core.Help" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Core.Help
</label>
<div class="md-nav__source">
<a href="https://github.com/Snider/Core" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../index.html" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="index.html" class="md-nav__link">
<span class="md-ellipsis">
Core
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Config
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="config.html" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Config
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#setup" class="md-nav__link">
<span class="md-ellipsis">
Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#use" class="md-nav__link">
<span class="md-ellipsis">
Use
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api" class="md-nav__link">
<span class="md-ellipsis">
API
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="crypt.html" class="md-nav__link">
<span class="md-ellipsis">
Crypt
</span>
</a>
</li>
<li class="md-nav__item">
<a href="display.html" class="md-nav__link">
<span class="md-ellipsis">
Display
</span>
</a>
</li>
<li class="md-nav__item">
<a href="docs.html" class="md-nav__link">
<span class="md-ellipsis">
Docs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="io.html" class="md-nav__link">
<span class="md-ellipsis">
IO
</span>
</a>
</li>
<li class="md-nav__item">
<a href="workspace.html" class="md-nav__link">
<span class="md-ellipsis">
Workspace
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#setup" class="md-nav__link">
<span class="md-ellipsis">
Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#use" class="md-nav__link">
<span class="md-ellipsis">
Use
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api" class="md-nav__link">
<span class="md-ellipsis">
API
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="coreconfig">Core.Config<a class="headerlink" href="#coreconfig" title="Permanent link">&para;</a></h1>
<p>Short: App config and UI state persistence.</p>
<h2 id="overview">Overview<a class="headerlink" href="#overview" title="Permanent link">&para;</a></h2>
<p>Stores and retrieves configuration, including window positions/sizes and user prefs.</p>
<h2 id="setup">Setup<a class="headerlink" href="#setup" title="Permanent link">&para;</a></h2>
<div class="language-go highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="kn">package</span><span class="w"> </span><span class="nx">main</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a>
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="kn">import</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="w"> </span><span class="nx">core</span><span class="w"> </span><span class="s">&quot;github.com/Snider/Core&quot;</span>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a><span class="w"> </span><span class="nx">config</span><span class="w"> </span><span class="s">&quot;github.com/Snider/Core/config&quot;</span>
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a><span class="p">)</span>
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a>
</span><span id="__span-0-8"><a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a><span class="nx">app</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">core</span><span class="p">.</span><span class="nx">New</span><span class="p">(</span>
</span><span id="__span-0-9"><a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a><span class="w"> </span><span class="nx">core</span><span class="p">.</span><span class="nx">WithService</span><span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">Register</span><span class="p">),</span>
</span><span id="__span-0-10"><a id="__codelineno-0-10" name="__codelineno-0-10" href="#__codelineno-0-10"></a><span class="w"> </span><span class="nx">core</span><span class="p">.</span><span class="nx">WithServiceLock</span><span class="p">(),</span>
</span><span id="__span-0-11"><a id="__codelineno-0-11" name="__codelineno-0-11" href="#__codelineno-0-11"></a><span class="p">)</span>
</span></code></pre></div>
<h2 id="use">Use<a class="headerlink" href="#use" title="Permanent link">&para;</a></h2>
<ul>
<li>Persist UI state automatically when using <code>Core.Display</code>.</li>
<li>Read/write your own settings via the config API.</li>
</ul>
<h2 id="api">API<a class="headerlink" href="#api" title="Permanent link">&para;</a></h2>
<ul>
<li><code>Register(c *core.Core) error</code></li>
<li><code>Get(path string, out any) error</code></li>
<li><code>Set(path string, v any) error</code></li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Core &copy; EUPL-1.2 &mdash; 2024 to &infin; and beyond
</div>
</div>
<div class="md-social">
<a href="https://github.com/Snider/Core" target="_blank" rel="noopener" title="github.com" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
</a>
<a href="https://dappco.re" target="_blank" rel="noopener" title="dappco.re" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M351.9 280H161c2.9 64.5 17.2 123.9 37.5 167.4 11.4 24.5 23.7 41.8 35.1 52.4 11.2 10.5 18.9 12.2 22.9 12.2s11.7-1.7 22.9-12.2c11.4-10.6 23.7-28 35.1-52.4 20.3-43.5 34.6-102.9 37.5-167.4zm-191-48h190.9c-2.8-64.5-17.1-123.9-37.4-167.4-11.4-24.4-23.7-41.8-35.1-52.4C268.1 1.7 260.4 0 256.4 0s-11.7 1.7-22.9 12.2c-11.4 10.6-23.7 28-35.1 52.4-20.3 43.5-34.6 102.9-37.5 167.4m-48 0c3.5-85.6 25.6-165.1 57.9-217.3C78.7 47.3 10.9 131.2 1.5 232zM1.5 280c9.4 100.8 77.2 184.7 169.3 217.3-32.3-52.2-54.4-131.7-57.9-217.3zm398.4 0c-3.5 85.6-25.6 165.1-57.9 217.3 92.1-32.7 159.9-116.5 169.3-217.3zm111.4-48C501.9 131.2 434.1 47.3 342 14.7c32.3 52.2 54.4 131.7 57.9 217.3z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.instant", "navigation.tracking", "navigation.tabs", "navigation.indexes", "navigation.expand", "navigation.sections", "navigation.path", "navigation.top", "search.suggest", "search.highlight", "search.share", "content.tabs.link", "content.code.copy"], "search": "../assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.f55a23d4.min.js"></script>
</body>
</html>

View file

@ -0,0 +1,905 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="canonical" href="https://dappco.re/core/crypt.html">
<link rel="prev" href="config.html">
<link rel="next" href="display.html">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.22">
<title>Core.Crypt - Core.Help</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
<script src="../assets/external/unpkg.com/iframe-worker/shim.js"></script>
<link rel="stylesheet" href="../assets/external/fonts.googleapis.com/css.49ea35f2.css">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../assets/stylesheets/extra.css">
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="blue" data-md-color-accent="blue">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#corecrypt" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../index.html" title="Core.Help" class="md-header__button md-logo" aria-label="Core.Help" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Core.Help
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Core.Crypt
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="blue" data-md-color-accent="blue" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="blue" data-md-color-accent="blue" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/Snider/Core" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../index.html" class="md-tabs__link">
Overview
</a>
</li>
<li class="md-tabs__item">
<a href="index.html" class="md-tabs__link">
Core
</a>
</li>
<li class="md-tabs__item">
<a href="config.html" class="md-tabs__link">
Config
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="crypt.html" class="md-tabs__link">
Crypt
</a>
</li>
<li class="md-tabs__item">
<a href="display.html" class="md-tabs__link">
Display
</a>
</li>
<li class="md-tabs__item">
<a href="docs.html" class="md-tabs__link">
Docs
</a>
</li>
<li class="md-tabs__item">
<a href="io.html" class="md-tabs__link">
IO
</a>
</li>
<li class="md-tabs__item">
<a href="workspace.html" class="md-tabs__link">
Workspace
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../index.html" title="Core.Help" class="md-nav__button md-logo" aria-label="Core.Help" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Core.Help
</label>
<div class="md-nav__source">
<a href="https://github.com/Snider/Core" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../index.html" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="index.html" class="md-nav__link">
<span class="md-ellipsis">
Core
</span>
</a>
</li>
<li class="md-nav__item">
<a href="config.html" class="md-nav__link">
<span class="md-ellipsis">
Config
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Crypt
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="crypt.html" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Crypt
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#setup" class="md-nav__link">
<span class="md-ellipsis">
Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#use" class="md-nav__link">
<span class="md-ellipsis">
Use
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api" class="md-nav__link">
<span class="md-ellipsis">
API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#notes" class="md-nav__link">
<span class="md-ellipsis">
Notes
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="display.html" class="md-nav__link">
<span class="md-ellipsis">
Display
</span>
</a>
</li>
<li class="md-nav__item">
<a href="docs.html" class="md-nav__link">
<span class="md-ellipsis">
Docs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="io.html" class="md-nav__link">
<span class="md-ellipsis">
IO
</span>
</a>
</li>
<li class="md-nav__item">
<a href="workspace.html" class="md-nav__link">
<span class="md-ellipsis">
Workspace
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#setup" class="md-nav__link">
<span class="md-ellipsis">
Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#use" class="md-nav__link">
<span class="md-ellipsis">
Use
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api" class="md-nav__link">
<span class="md-ellipsis">
API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#notes" class="md-nav__link">
<span class="md-ellipsis">
Notes
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="corecrypt">Core.Crypt<a class="headerlink" href="#corecrypt" title="Permanent link">&para;</a></h1>
<p>Short: Keys, encrypt/decrypt, sign/verify.</p>
<h2 id="overview">Overview<a class="headerlink" href="#overview" title="Permanent link">&para;</a></h2>
<p>Simple wrappers around OpenPGP for common crypto tasks.</p>
<h2 id="setup">Setup<a class="headerlink" href="#setup" title="Permanent link">&para;</a></h2>
<div class="language-go highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="kn">import</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="w"> </span><span class="nx">core</span><span class="w"> </span><span class="s">&quot;github.com/Snider/Core&quot;</span>
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="w"> </span><span class="nx">crypt</span><span class="w"> </span><span class="s">&quot;github.com/Snider/Core/crypt&quot;</span>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="p">)</span>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a><span class="nx">app</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">core</span><span class="p">.</span><span class="nx">New</span><span class="p">(</span>
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a><span class="w"> </span><span class="nx">core</span><span class="p">.</span><span class="nx">WithService</span><span class="p">(</span><span class="nx">crypt</span><span class="p">.</span><span class="nx">Register</span><span class="p">),</span>
</span><span id="__span-0-8"><a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a><span class="w"> </span><span class="nx">core</span><span class="p">.</span><span class="nx">WithServiceLock</span><span class="p">(),</span>
</span><span id="__span-0-9"><a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a><span class="p">)</span>
</span></code></pre></div>
<h2 id="use">Use<a class="headerlink" href="#use" title="Permanent link">&para;</a></h2>
<ul>
<li>Generate keys</li>
<li>Encrypt/decrypt data</li>
<li>Sign/verify messages</li>
</ul>
<h2 id="api">API<a class="headerlink" href="#api" title="Permanent link">&para;</a></h2>
<ul>
<li><code>Register(c *core.Core) error</code></li>
<li><code>GenerateKey(opts ...Option) (*Key, error)</code></li>
<li><code>Encrypt(pub *Key, data []byte) ([]byte, error)</code></li>
<li><code>Decrypt(priv *Key, data []byte) ([]byte, error)</code></li>
<li><code>Sign(priv *Key, data []byte) ([]byte, error)</code></li>
<li><code>Verify(pub *Key, data, sig []byte) error</code></li>
</ul>
<h2 id="notes">Notes<a class="headerlink" href="#notes" title="Permanent link">&para;</a></h2>
<ul>
<li>Uses <a href="https://pr.tn/ref/VZFX8H2VDCFG">ProtonMail</a> OpenPGP fork.</li>
</ul>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Core &copy; EUPL-1.2 &mdash; 2024 to &infin; and beyond
</div>
</div>
<div class="md-social">
<a href="https://github.com/Snider/Core" target="_blank" rel="noopener" title="github.com" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
</a>
<a href="https://dappco.re" target="_blank" rel="noopener" title="dappco.re" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M351.9 280H161c2.9 64.5 17.2 123.9 37.5 167.4 11.4 24.5 23.7 41.8 35.1 52.4 11.2 10.5 18.9 12.2 22.9 12.2s11.7-1.7 22.9-12.2c11.4-10.6 23.7-28 35.1-52.4 20.3-43.5 34.6-102.9 37.5-167.4zm-191-48h190.9c-2.8-64.5-17.1-123.9-37.4-167.4-11.4-24.4-23.7-41.8-35.1-52.4C268.1 1.7 260.4 0 256.4 0s-11.7 1.7-22.9 12.2c-11.4 10.6-23.7 28-35.1 52.4-20.3 43.5-34.6 102.9-37.5 167.4m-48 0c3.5-85.6 25.6-165.1 57.9-217.3C78.7 47.3 10.9 131.2 1.5 232zM1.5 280c9.4 100.8 77.2 184.7 169.3 217.3-32.3-52.2-54.4-131.7-57.9-217.3zm398.4 0c-3.5 85.6-25.6 165.1-57.9 217.3 92.1-32.7 159.9-116.5 169.3-217.3zm111.4-48C501.9 131.2 434.1 47.3 342 14.7c32.3 52.2 54.4 131.7 57.9 217.3z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.instant", "navigation.tracking", "navigation.tabs", "navigation.indexes", "navigation.expand", "navigation.sections", "navigation.path", "navigation.top", "search.suggest", "search.highlight", "search.share", "content.tabs.link", "content.code.copy"], "search": "../assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.f55a23d4.min.js"></script>
</body>
</html>

View file

@ -0,0 +1,907 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="canonical" href="https://dappco.re/core/display.html">
<link rel="prev" href="crypt.html">
<link rel="next" href="docs.html">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.22">
<title>Core.Display - Core.Help</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
<script src="../assets/external/unpkg.com/iframe-worker/shim.js"></script>
<link rel="stylesheet" href="../assets/external/fonts.googleapis.com/css.49ea35f2.css">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../assets/stylesheets/extra.css">
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="blue" data-md-color-accent="blue">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#coredisplay" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../index.html" title="Core.Help" class="md-header__button md-logo" aria-label="Core.Help" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Core.Help
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Core.Display
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="blue" data-md-color-accent="blue" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="blue" data-md-color-accent="blue" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/Snider/Core" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href="../index.html" class="md-tabs__link">
Overview
</a>
</li>
<li class="md-tabs__item">
<a href="index.html" class="md-tabs__link">
Core
</a>
</li>
<li class="md-tabs__item">
<a href="config.html" class="md-tabs__link">
Config
</a>
</li>
<li class="md-tabs__item">
<a href="crypt.html" class="md-tabs__link">
Crypt
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="display.html" class="md-tabs__link">
Display
</a>
</li>
<li class="md-tabs__item">
<a href="docs.html" class="md-tabs__link">
Docs
</a>
</li>
<li class="md-tabs__item">
<a href="io.html" class="md-tabs__link">
IO
</a>
</li>
<li class="md-tabs__item">
<a href="workspace.html" class="md-tabs__link">
Workspace
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../index.html" title="Core.Help" class="md-nav__button md-logo" aria-label="Core.Help" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Core.Help
</label>
<div class="md-nav__source">
<a href="https://github.com/Snider/Core" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
GitHub
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../index.html" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="index.html" class="md-nav__link">
<span class="md-ellipsis">
Core
</span>
</a>
</li>
<li class="md-nav__item">
<a href="config.html" class="md-nav__link">
<span class="md-ellipsis">
Config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="crypt.html" class="md-nav__link">
<span class="md-ellipsis">
Crypt
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Display
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="display.html" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Display
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#setup" class="md-nav__link">
<span class="md-ellipsis">
Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#use" class="md-nav__link">
<span class="md-ellipsis">
Use
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api" class="md-nav__link">
<span class="md-ellipsis">
API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#example" class="md-nav__link">
<span class="md-ellipsis">
Example
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="docs.html" class="md-nav__link">
<span class="md-ellipsis">
Docs
</span>
</a>
</li>
<li class="md-nav__item">
<a href="io.html" class="md-nav__link">
<span class="md-ellipsis">
IO
</span>
</a>
</li>
<li class="md-nav__item">
<a href="workspace.html" class="md-nav__link">
<span class="md-ellipsis">
Workspace
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#overview" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#setup" class="md-nav__link">
<span class="md-ellipsis">
Setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#use" class="md-nav__link">
<span class="md-ellipsis">
Use
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api" class="md-nav__link">
<span class="md-ellipsis">
API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#example" class="md-nav__link">
<span class="md-ellipsis">
Example
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="coredisplay">Core.Display<a class="headerlink" href="#coredisplay" title="Permanent link">&para;</a></h1>
<p>Short: Windows, tray, and window state.</p>
<h2 id="overview">Overview<a class="headerlink" href="#overview" title="Permanent link">&para;</a></h2>
<p>Manages Wails windows, remembers positions/sizes, exposes JS bindings, and integrates with <code>Core.Config</code> for persistence.</p>
<h2 id="setup">Setup<a class="headerlink" href="#setup" title="Permanent link">&para;</a></h2>
<div class="language-go highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="kn">import</span><span class="w"> </span><span class="p">(</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a><span class="w"> </span><span class="nx">core</span><span class="w"> </span><span class="s">&quot;github.com/Snider/Core&quot;</span>
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="w"> </span><span class="nx">display</span><span class="w"> </span><span class="s">&quot;github.com/Snider/Core/display&quot;</span>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="p">)</span>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a><span class="nx">app</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">core</span><span class="p">.</span><span class="nx">New</span><span class="p">(</span>
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a><span class="w"> </span><span class="nx">core</span><span class="p">.</span><span class="nx">WithService</span><span class="p">(</span><span class="nx">display</span><span class="p">.</span><span class="nx">Register</span><span class="p">),</span>
</span><span id="__span-0-8"><a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a><span class="w"> </span><span class="nx">core</span><span class="p">.</span><span class="nx">WithServiceLock</span><span class="p">(),</span>
</span><span id="__span-0-9"><a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a><span class="p">)</span>
</span></code></pre></div>
<h2 id="use">Use<a class="headerlink" href="#use" title="Permanent link">&para;</a></h2>
<ul>
<li>Open a window: <code>OpenWindow(OptName("main"), ...)</code></li>
<li>Get a window: <code>Window("main")</code></li>
<li>Save/restore state automatically when <code>Core.Config</code> is present</li>
</ul>
<h2 id="api">API<a class="headerlink" href="#api" title="Permanent link">&para;</a></h2>
<ul>
<li><code>Register(c *core.Core) error</code></li>
<li><code>OpenWindow(opts ...Option) *Window</code></li>
<li><code>Window(name string) *Window</code></li>
<li>Options: <code>OptName</code>, <code>OptWidth</code>, <code>OptHeight</code>, <code>OptURL</code>, <code>OptTitle</code></li>
</ul>
<h2 id="example">Example<a class="headerlink" href="#example" title="Permanent link">&para;</a></h2>
<div class="language-go highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">d</span><span class="w"> </span><span class="o">*</span><span class="nx">API</span><span class="p">)</span><span class="w"> </span><span class="nx">ServiceStartup</span><span class="p">(</span><span class="nx">ctx</span><span class="w"> </span><span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="nx">application</span><span class="p">.</span><span class="nx">ServiceOptions</span><span class="p">)</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span>
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2" href="#__codelineno-1-2"></a><span class="w"> </span><span class="nx">d</span><span class="p">.</span><span class="nx">OpenWindow</span><span class="p">(</span>
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3" href="#__codelineno-1-3"></a><span class="w"> </span><span class="nx">OptName</span><span class="p">(</span><span class="s">&quot;main&quot;</span><span class="p">),</span><span class="w"> </span><span class="nx">OptWidth</span><span class="p">(</span><span class="mi">1280</span><span class="p">),</span><span class="w"> </span><span class="nx">OptHeight</span><span class="p">(</span><span class="mi">900</span><span class="p">),</span><span class="w"> </span><span class="nx">OptURL</span><span class="p">(</span><span class="s">&quot;/&quot;</span><span class="p">),</span><span class="w"> </span><span class="nx">OptTitle</span><span class="p">(</span><span class="s">&quot;Core&quot;</span><span class="p">),</span>
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4" href="#__codelineno-1-4"></a><span class="w"> </span><span class="p">)</span>
</span><span id="__span-1-5"><a id="__codelineno-1-5" name="__codelineno-1-5" href="#__codelineno-1-5"></a><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span>
</span><span id="__span-1-6"><a id="__codelineno-1-6" name="__codelineno-1-6" href="#__codelineno-1-6"></a><span class="p">}</span>
</span></code></pre></div>
</article>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Core &copy; EUPL-1.2 &mdash; 2024 to &infin; and beyond
</div>
</div>
<div class="md-social">
<a href="https://github.com/Snider/Core" target="_blank" rel="noopener" title="github.com" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M173.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9M252.8 8C114.1 8 8 113.3 8 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C436.2 457.8 504 362.9 504 252 504 113.3 391.5 8 252.8 8M105.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2"/></svg>
</a>
<a href="https://dappco.re" target="_blank" rel="noopener" title="dappco.re" class="md-social__link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M351.9 280H161c2.9 64.5 17.2 123.9 37.5 167.4 11.4 24.5 23.7 41.8 35.1 52.4 11.2 10.5 18.9 12.2 22.9 12.2s11.7-1.7 22.9-12.2c11.4-10.6 23.7-28 35.1-52.4 20.3-43.5 34.6-102.9 37.5-167.4zm-191-48h190.9c-2.8-64.5-17.1-123.9-37.4-167.4-11.4-24.4-23.7-41.8-35.1-52.4C268.1 1.7 260.4 0 256.4 0s-11.7 1.7-22.9 12.2c-11.4 10.6-23.7 28-35.1 52.4-20.3 43.5-34.6 102.9-37.5 167.4m-48 0c3.5-85.6 25.6-165.1 57.9-217.3C78.7 47.3 10.9 131.2 1.5 232zM1.5 280c9.4 100.8 77.2 184.7 169.3 217.3-32.3-52.2-54.4-131.7-57.9-217.3zm398.4 0c-3.5 85.6-25.6 165.1-57.9 217.3 92.1-32.7 159.9-116.5 169.3-217.3zm111.4-48C501.9 131.2 434.1 47.3 342 14.7c32.3 52.2 54.4 131.7 57.9 217.3z"/></svg>
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.instant", "navigation.tracking", "navigation.tabs", "navigation.indexes", "navigation.expand", "navigation.sections", "navigation.path", "navigation.top", "search.suggest", "search.highlight", "search.share", "content.tabs.link", "content.code.copy"], "search": "../assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.f55a23d4.min.js"></script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more