From 2ba548810c6ce1ced2df9eb19f2cea03fa96bd9e Mon Sep 17 00:00:00 2001 From: Andrew Barba <barba@hey.com> Date: Mon, 23 May 2022 16:03:28 -0400 Subject: [PATCH] Support meta tags in StaticHTMLRenderer (#483) This PR adds the ability to control `<head>` tags using a new `HTMLTitle` view and `HTMLMeta` view. Taking inspiration from Next.js `<NextHead>`, you can use these views anywhere in your view hierarchy and they will be hoisted to the top `<head>` section of the html. Use as a view: ```swift var body: some View { VStack { ... HTMLTitle("Hello, Tokamak") HTMLMeta(charset: "utf-8") ... } } ``` Use as a view modifier: ```swift var body: some View { VStack { ... } .htmlTitle("Hello, Tokamak") .htmlMeta(charset: "utf-8") } ``` And the resulting html (no matter where these are used in your view hierarchy): ```html <html> <head> <title>Hello, Tokamak</title> <meta charset="utf-8"> </head> <body> ... </body> </html> ``` --- .../MountedViews/MountedCompositeView.swift | 3 - .../MountedViews/MountedElement.swift | 17 +- .../Preferences/PreferenceKey.swift | 19 +- Sources/TokamakCore/StackReconciler.swift | 6 + .../TokamakCore/Views/Containers/Group.swift | 2 +- Sources/TokamakStaticHTML/App.swift | 2 +- .../Preferences/Preferences.swift | 37 +++ .../StaticHTMLRenderer.swift | 26 +- .../TokamakStaticHTML/Views/Head/Meta.swift | 83 ++++++ .../TokamakStaticHTML/Views/Head/Title.swift | 44 +++ Tests/TokamakStaticHTMLTests/HTMLTests.swift | 114 ++++++++ .../HTMLTests/testDoubleTitle.1.html | 267 ++++++++++++++++++ .../HTMLTests/testDoubleTitleModifier.1.html | 260 +++++++++++++++++ .../HTMLTests/testMetaAll.1.html | 266 +++++++++++++++++ .../HTMLTests/testMetaCharset.1.html | 263 +++++++++++++++++ .../HTMLTests/testMetaCharsetModifier.1.html | 260 +++++++++++++++++ .../testPreferencePropagation.1.html | 261 +++++++++++++++++ .../__Snapshots__/HTMLTests/testTitle.1.html | 263 +++++++++++++++++ .../HTMLTests/testTitleModifier.1.html | 260 +++++++++++++++++ 19 files changed, 2425 insertions(+), 28 deletions(-) create mode 100644 Sources/TokamakStaticHTML/Preferences/Preferences.swift create mode 100644 Sources/TokamakStaticHTML/Views/Head/Meta.swift create mode 100644 Sources/TokamakStaticHTML/Views/Head/Title.swift create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testDoubleTitle.1.html create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testDoubleTitleModifier.1.html create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaAll.1.html create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaCharset.1.html create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaCharsetModifier.1.html create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPreferencePropagation.1.html create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testTitle.1.html create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testTitleModifier.1.html diff --git a/Sources/TokamakCore/MountedViews/MountedCompositeView.swift b/Sources/TokamakCore/MountedViews/MountedCompositeView.swift index 4aad0b8bb..9415fe72a 100644 --- a/Sources/TokamakCore/MountedViews/MountedCompositeView.swift +++ b/Sources/TokamakCore/MountedViews/MountedCompositeView.swift @@ -78,9 +78,6 @@ final class MountedCompositeView<R: Renderer>: MountedCompositeElement<R> { if let preferenceModifier = self.view.view as? _PreferenceWritingViewProtocol { self.view = preferenceModifier.modifyPreferenceStore(&self.preferenceStore) - if let parent = parent { - parent.preferenceStore.merge(with: self.preferenceStore) - } } if let preferenceReader = self.view.view as? _PreferenceReadingViewProtocol { diff --git a/Sources/TokamakCore/MountedViews/MountedElement.swift b/Sources/TokamakCore/MountedViews/MountedElement.swift index 52959a0d4..70e52c92c 100644 --- a/Sources/TokamakCore/MountedViews/MountedElement.swift +++ b/Sources/TokamakCore/MountedViews/MountedElement.swift @@ -94,13 +94,9 @@ public class MountedElement<R: Renderer> { public internal(set) var environmentValues: EnvironmentValues - weak var parent: MountedElement<R>? - /// `didSet` on this field propagates the preference changes up the view tree. - var preferenceStore: _PreferenceStore = .init() { - didSet { - parent?.preferenceStore.merge(with: preferenceStore) - } - } + private(set) weak var parent: MountedElement<R>? + + var preferenceStore: _PreferenceStore = .init() public internal(set) var viewTraits: _ViewTraitStore @@ -110,6 +106,7 @@ public class MountedElement<R: Renderer> { self.environmentValues = environmentValues viewTraits = .init() updateEnvironment() + connectParentPreferenceStore() } init(_ scene: _AnyScene, _ environmentValues: EnvironmentValues, _ parent: MountedElement<R>?) { @@ -118,6 +115,7 @@ public class MountedElement<R: Renderer> { self.environmentValues = environmentValues viewTraits = .init() updateEnvironment() + connectParentPreferenceStore() } init( @@ -131,6 +129,7 @@ public class MountedElement<R: Renderer> { self.environmentValues = environmentValues self.viewTraits = viewTraits updateEnvironment() + connectParentPreferenceStore() } func updateEnvironment() { @@ -145,6 +144,10 @@ public class MountedElement<R: Renderer> { } } + func connectParentPreferenceStore() { + preferenceStore.parent = parent?.preferenceStore + } + /// You must call `super.prepareForMount` before all other mounting work. func prepareForMount(with transaction: Transaction) { // `GroupView`'s don't really mount, so let their children transition if the group can. diff --git a/Sources/TokamakCore/Preferences/PreferenceKey.swift b/Sources/TokamakCore/Preferences/PreferenceKey.swift index a9ddc22f9..d8ca9a658 100644 --- a/Sources/TokamakCore/Preferences/PreferenceKey.swift +++ b/Sources/TokamakCore/Preferences/PreferenceKey.swift @@ -48,10 +48,12 @@ public extension _PreferenceValue { } } -public struct _PreferenceStore { +public final class _PreferenceStore { /// The backing values of the `_PreferenceStore`. private var values: [String: Any] + weak var parent: _PreferenceStore? + public init(values: [String: Any] = [:]) { self.values = values } @@ -63,23 +65,12 @@ public struct _PreferenceStore { ?? _PreferenceValue(valueList: [Key.defaultValue]) } - public mutating func insert<Key>(_ value: Key.Value, forKey key: Key.Type = Key.self) + public func insert<Key>(_ value: Key.Value, forKey key: Key.Type = Key.self) where Key: PreferenceKey { let previousValues = self.value(forKey: key).valueList values[String(reflecting: key)] = _PreferenceValue<Key>(valueList: previousValues + [value]) - } - - public mutating func merge(with other: Self) { - self = merging(with: other) - } - - public func merging(with other: Self) -> Self { - var result = values - for (key, value) in other.values { - result[key] = value - } - return .init(values: result) + parent?.insert(value, forKey: key) } } diff --git a/Sources/TokamakCore/StackReconciler.swift b/Sources/TokamakCore/StackReconciler.swift index b4c9c4bac..71f84e1f5 100644 --- a/Sources/TokamakCore/StackReconciler.swift +++ b/Sources/TokamakCore/StackReconciler.swift @@ -58,6 +58,12 @@ public final class StackReconciler<R: Renderer> { */ public let rootTarget: R.TargetType + /** A root renderer's main preference store. + */ + public var preferenceStore: _PreferenceStore { + rootElement.preferenceStore + } + /** A root of the mounted elements tree to which all other mounted elements are attached to. */ private let rootElement: MountedElement<R> diff --git a/Sources/TokamakCore/Views/Containers/Group.swift b/Sources/TokamakCore/Views/Containers/Group.swift index 68d84606d..e27e2629c 100644 --- a/Sources/TokamakCore/Views/Containers/Group.swift +++ b/Sources/TokamakCore/Views/Containers/Group.swift @@ -19,7 +19,7 @@ public struct Group<Content> { } } -extension Group: _PrimitiveView & View where Content: View {} +extension Group: _PrimitiveView, View where Content: View {} extension Group: ParentView where Content: View { @_spi(TokamakCore) diff --git a/Sources/TokamakStaticHTML/App.swift b/Sources/TokamakStaticHTML/App.swift index 22b509099..c99e667bc 100644 --- a/Sources/TokamakStaticHTML/App.swift +++ b/Sources/TokamakStaticHTML/App.swift @@ -24,7 +24,7 @@ public extension App { } static func _setTitle(_ title: String) { - StaticHTMLRenderer.title = title + // no-op: use Title view } var _phasePublisher: AnyPublisher<ScenePhase, Never> { diff --git a/Sources/TokamakStaticHTML/Preferences/Preferences.swift b/Sources/TokamakStaticHTML/Preferences/Preferences.swift new file mode 100644 index 000000000..94d242785 --- /dev/null +++ b/Sources/TokamakStaticHTML/Preferences/Preferences.swift @@ -0,0 +1,37 @@ +// Copyright 2022 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Andrew Barba on 5/20/22. +// + +import TokamakCore + +public struct HTMLTitlePreferenceKey: PreferenceKey { + public static var defaultValue: String = "" + + public static func reduce(value: inout String, nextValue: () -> String) { + value = nextValue() + } +} + +public struct HTMLMetaPreferenceKey: PreferenceKey { + public static var defaultValue: [HTMLMeta.MetaTag] = [] + + public static func reduce( + value: inout [HTMLMeta.MetaTag], + nextValue: () -> [HTMLMeta.MetaTag] + ) { + value += nextValue() + } +} diff --git a/Sources/TokamakStaticHTML/StaticHTMLRenderer.swift b/Sources/TokamakStaticHTML/StaticHTMLRenderer.swift index 6e393c346..d45d3acb3 100644 --- a/Sources/TokamakStaticHTML/StaticHTMLRenderer.swift +++ b/Sources/TokamakStaticHTML/StaticHTMLRenderer.swift @@ -58,18 +58,40 @@ struct HTMLBody: AnyHTML { ] } +extension HTMLMeta.MetaTag { + func outerHTML() -> String { + switch self { + case let .charset(charset): + return #"<meta charset="\#(charset)">"# + case let .name(name, content): + return #"<meta name="\#(name)" content="\#(content)">"# + case let .property(property, content): + return #"<meta property="\#(property)" content="\#(content)">"# + case let .httpEquiv(httpEquiv, content): + return #"<meta http-equiv="\#(httpEquiv)" content="\#(content)">"# + } + } +} + public final class StaticHTMLRenderer: Renderer { private var reconciler: StackReconciler<StaticHTMLRenderer>? var rootTarget: HTMLTarget - static var title: String = "" + var title: String { + reconciler?.preferenceStore.value(forKey: HTMLTitlePreferenceKey.self).value ?? "" + } + + var meta: [HTMLMeta.MetaTag] { + reconciler?.preferenceStore.value(forKey: HTMLMetaPreferenceKey.self).value ?? [] + } public func render(shouldSortAttributes: Bool = false) -> String { """ <html> <head> - <title>\(Self.title)</title> + <title>\(title)</title> + \(meta.map { $0.outerHTML() }.joined(separator: "\n ")) <style> \(tokamakStyles) </style> diff --git a/Sources/TokamakStaticHTML/Views/Head/Meta.swift b/Sources/TokamakStaticHTML/Views/Head/Meta.swift new file mode 100644 index 000000000..729a3c8f3 --- /dev/null +++ b/Sources/TokamakStaticHTML/Views/Head/Meta.swift @@ -0,0 +1,83 @@ +// Copyright 2022 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Andrew Barba on 5/20/22. +// + +import TokamakCore + +public struct HTMLMeta: View { + public enum MetaTag: Equatable, Hashable { + case charset(_ charset: String) + case name(_ name: String, content: String) + case property(_ property: String, content: String) + case httpEquiv(_ httpEquiv: String, content: String) + } + + var meta: MetaTag + + public init(_ value: MetaTag) { + meta = value + } + + public init(charset: String) { + meta = .charset(charset) + } + + public init(name: String, content: String) { + meta = .name(name, content: content) + } + + public init(property: String, content: String) { + meta = .property(property, content: content) + } + + public init(httpEquiv: String, content: String) { + meta = .httpEquiv(httpEquiv, content: content) + } + + public var body: some View { + EmptyView() + .preference(key: HTMLMetaPreferenceKey.self, value: [meta]) + } +} + +public extension View { + func htmlMeta(_ value: HTMLMeta.MetaTag) -> some View { + htmlMeta(.init(value)) + } + + func htmlMeta(charset: String) -> some View { + htmlMeta(.init(charset: charset)) + } + + func htmlMeta(name: String, content: String) -> some View { + htmlMeta(.init(name: name, content: content)) + } + + func htmlMeta(property: String, content: String) -> some View { + htmlMeta(.init(property: property, content: content)) + } + + func htmlMeta(httpEquiv: String, content: String) -> some View { + htmlMeta(.init(httpEquiv: httpEquiv, content: content)) + } + + func htmlMeta(_ meta: HTMLMeta) -> some View { + Group { + self + meta + } + } +} diff --git a/Sources/TokamakStaticHTML/Views/Head/Title.swift b/Sources/TokamakStaticHTML/Views/Head/Title.swift new file mode 100644 index 000000000..fb69b39b5 --- /dev/null +++ b/Sources/TokamakStaticHTML/Views/Head/Title.swift @@ -0,0 +1,44 @@ +// Copyright 2022 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Andrew Barba on 5/20/22. +// + +import TokamakCore + +public struct HTMLTitle: View { + var title: String + + public init(_ title: String) { + self.title = title + } + + public var body: some View { + EmptyView() + .preference(key: HTMLTitlePreferenceKey.self, value: title) + } +} + +public extension View { + func htmlTitle(_ title: String) -> some View { + htmlTitle(.init(title)) + } + + func htmlTitle(_ title: HTMLTitle) -> some View { + Group { + self + title + } + } +} diff --git a/Tests/TokamakStaticHTMLTests/HTMLTests.swift b/Tests/TokamakStaticHTMLTests/HTMLTests.swift index 724986ede..e28ac4f30 100644 --- a/Tests/TokamakStaticHTMLTests/HTMLTests.swift +++ b/Tests/TokamakStaticHTMLTests/HTMLTests.swift @@ -92,6 +92,120 @@ final class HTMLTests: XCTestCase { .render(shouldSortAttributes: true) assertSnapshot(matching: insecureHTML, as: .html) } + + func testTitle() { + let resultingHTML = StaticHTMLRenderer( + VStack { + HTMLTitle("Tokamak") + Text("Hello, world!") + } + ).render(shouldSortAttributes: true) + + assert(resultingHTML.contains("<title>Tokamak</title>")) + assertSnapshot(matching: resultingHTML, as: .html) + } + + func testDoubleTitle() { + let resultingHTML = StaticHTMLRenderer( + VStack { + HTMLTitle("Tokamak 1") + Text("Hello, world!") + VStack { + HTMLTitle("Tokamak 2") + } + } + ).render(shouldSortAttributes: true) + + assert(resultingHTML.contains("<title>Tokamak 2</title>") == true) + assert(resultingHTML.contains("<title>Tokamak 1</title>") == false) + assertSnapshot(matching: resultingHTML, as: .html) + } + + func testTitleModifier() { + let resultingHTML = StaticHTMLRenderer( + Text("Hello, world!") + .htmlTitle("Tokamak") + ).render(shouldSortAttributes: true) + + assert(resultingHTML.contains("<title>Tokamak</title>")) + assertSnapshot(matching: resultingHTML, as: .html) + } + + func testDoubleTitleModifier() { + let resultingHTML = StaticHTMLRenderer( + Text("Hello, world!") + .htmlTitle("Tokamak 1") + .htmlTitle("Tokamak 2") + ).render(shouldSortAttributes: true) + + assert(resultingHTML.contains("<title>Tokamak 2</title>") == true) + assert(resultingHTML.contains("<title>Tokamak 1</title>") == false) + assertSnapshot(matching: resultingHTML, as: .html) + } + + func testMetaCharset() { + let resultingHTML = StaticHTMLRenderer( + VStack { + HTMLMeta(charset: "utf-8") + Text("Hello, world!") + } + ).render(shouldSortAttributes: true) + + assertSnapshot(matching: resultingHTML, as: .html) + } + + func testMetaCharsetModifier() { + let resultingHTML = StaticHTMLRenderer( + Text("Hello, world!") + .htmlMeta(charset: "utf-8") + ).render(shouldSortAttributes: true) + + assertSnapshot(matching: resultingHTML, as: .html) + } + + func testMetaAll() { + let resultingHTML = StaticHTMLRenderer( + VStack { + HTMLMeta(charset: "utf-8") + HTMLMeta(name: "description", content: "SwiftUI on the web") + HTMLMeta(property: "og:image", content: "https://image.png") + HTMLMeta(httpEquiv: "refresh", content: "60") + Text("Hello, world!") + } + ).render(shouldSortAttributes: true) + + assert(resultingHTML.components(separatedBy: "<meta ").count == 5) + assertSnapshot(matching: resultingHTML, as: .html) + } + + func testPreferencePropagation() { + var title0 = "" + var title1 = "" + var title2 = "" + var title3 = "" + + let resultingHTML = StaticHTMLRenderer( + VStack { + HTMLTitle("Tokamak 1") + .onPreferenceChange(HTMLTitlePreferenceKey.self) { title1 = $0 } + VStack { + HTMLTitle("Tokamak 2") + } + .onPreferenceChange(HTMLTitlePreferenceKey.self) { title2 = $0 } + VStack { + HTMLTitle("Tokamak 3") + } + .onPreferenceChange(HTMLTitlePreferenceKey.self) { title3 = $0 } + } + .onPreferenceChange(HTMLTitlePreferenceKey.self) { title0 = $0 } + ).render(shouldSortAttributes: true) + + assert(title0 == "Tokamak 3") + assert(title1 == "Tokamak 1") + assert(title2 == "Tokamak 2") + assert(title3 == "Tokamak 3") + assertSnapshot(matching: resultingHTML, as: .html) + } } #endif diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testDoubleTitle.1.html b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testDoubleTitle.1.html new file mode 100644 index 000000000..a4d8c4d1a --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testDoubleTitle.1.html @@ -0,0 +1,267 @@ +<html> +<head> + <title>Tokamak 2</title> + + <style> + ._tokamak-stack { + display: grid; +} +._tokamak-hstack { + grid-auto-flow: column; + column-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-vstack { + grid-auto-flow: row; + row-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-scrollview-hideindicators { + scrollbar-color: transparent; + scrollbar-width: 0; +} +._tokamak-scrollview-hideindicators::-webkit-scrollbar { + width: 0; + height: 0; +} +._tokamak-list { + list-style: none; + overflow-y: auto; + width: 100%; + height: 100%; + padding: 0; +} +._tokamak-disclosuregroup-label { + cursor: pointer; +} +._tokamak-disclosuregroup-chevron-container { + width: .25em; + height: .25em; + padding: 10px; + display: inline-block; +} +._tokamak-disclosuregroup-chevron { + width: 100%; + height: 100%; + transform: rotate(45deg); + border-right: solid 2px rgba(0, 0, 0, 0.25); + border-top: solid 2px rgba(0, 0, 0, 0.25); +} +._tokamak-disclosuregroup-content { + display: flex; + flex-direction: column; + margin-left: 1em; +} +._tokamak-buttonstyle-reset { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + border: none; + margin: 0; + padding: 0; + font-size: inherit; +} +@supports (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + -webkit-appearance: default-button; + } +} +@supports not (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + background-color: rgb(55, 120, 246); + border: 1px solid rgb(88, 156, 248); + border-radius: 4px; + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(38, 99, 226); + } + + @media (prefers-color-scheme:dark) { + ._tokamak-button-prominence-increased { + background-color: rgb(56, 116, 225); + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(56, 134, 247); + } + } +} + +._tokamak-text-redacted { + position: relative; +} +._tokamak-text-redacted::after { + content: " "; + background-color: rgb(200, 200, 200); + position: absolute; + left: 0; + width: calc(100% + .1em); + height: 1.2em; + border-radius: .1em; +} +._tokamak-geometryreader { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +._tokamak-navigationview { + display: flex; + flex-direction: row; + align-items: stretch; + width: 100%; + height: 100%; +} +._tokamak-navigationview-with-toolbar-content ._tokamak-scrollview { + padding-top: 50px; +} +._tokamak-navigationview-destination { + display: flex; flex-direction: column; + align-items: center; justify-content: center; + flex-grow: 1; + height: 100%; +} + +._tokamak-toolbar { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 50px; + display: flex; + align-items: center; + overflow: hidden; + background: rgba(200, 200, 200, 0.2); + -webkit-backdrop-filter: saturate(180%) blur(20px); + backdrop-filter: saturate(180%) blur(20px); +} + +._tokamak-toolbar-content { + flex: 1 1 auto; + display: flex; + height: 100%; +} +._tokamak-toolbar-leading > *, ._tokamak-toolbar-center > * { + margin-right: 8px; +} +._tokamak-toolbar-trailing > * { + margin-left: 8px; +} +._tokamak-toolbar-leading { + margin-right: auto; + align-items: center; + justify-content: flex-start; + padding-left: 16px; +} +._tokamak-toolbar-center { + align-items: center; + justify-content: center; +} +._tokamak-toolbar-trailing { + margin-left: auto; + align-items: center; + justify-content: flex-end; + padding-right: 16px; +} + +._tokamak-toolbar-button { + padding: 2px 4px; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.05); + height: 25px; + padding: 0 8px; + display: flex; + align-items: center; +} +._tokamak-toolbar-button:hover { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.05); +} +._tokamak-toolbar-button:active { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.1); +} +._tokamak-toolbar-textfield input { + padding: 4px 4px 4px 8px; + border-radius: 3px; + background-color: rgba(0, 0, 0, 0.05); + width: 100%; + height: 100%; +} + +._tokamak-formcontrol, ._tokamak-formcontrol-reset { + color-scheme: light dark; +} +._tokamak-formcontrol-reset { + background: none; + border: none; +} + +._tokamak-link { + text-decoration: none; +} + +._tokamak-texteditor { + width: 100%; + height: 100%; +} + +._tokamak-aspect-ratio-fill > img { + object-fit: fill; +} + +._tokamak-aspect-ratio-fit > img { + object-fit: contain; +} + +@media (prefers-color-scheme:dark) { + ._tokamak-text-redacted::after { + background-color: rgb(100, 100, 100); + } + + ._tokamak-disclosuregroup-chevron { + border-right-color: rgba(255, 255, 255, 0.25); + border-top-color: rgba(255, 255, 255, 0.25); + } + + ._tokamak-toolbar { + background: rgba(100, 100, 100, 0.2); + } + ._tokamak-toolbar-button { + border: 1px solid rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-button:hover { + background-color: rgba(255, 255, 255, 0.05); + } + ._tokamak-toolbar-button:active { + background-color: rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-textfield input { + background-color: rgba(255, 255, 255, 0.05); + color: #FFFFFF; + } +} + </style> +</head> +<body style="margin: 0;display: flex; +width: 100%; +height: 100%; +justify-content: center; +align-items: center; +overflow: hidden;"><div class="_tokamak-stack _tokamak-vstack" style="justify-items: center; + + +"><span style="font-family: system, -apple-system, '.SFNSText-Regular', 'San Francisco', 'Roboto', 'Segoe UI', 'Helvetica Neue', 'Lucida Grande', sans-serif; font-size: 17.0; font-style: normal; font-variant: normal; font-weight: 400; line-height: normal; + +color: rgba(0.0, 0.0, 0.0, 1.0); +font-style: normal; +font-weight: 400; +letter-spacing: normal; +vertical-align: baseline; +text-decoration: none; +text-decoration-color: inherit; +text-align: left;">Hello, world!</span> +<div class="_tokamak-stack _tokamak-vstack" style="justify-items: center; + + +"></div></div></body> +</html> \ No newline at end of file diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testDoubleTitleModifier.1.html b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testDoubleTitleModifier.1.html new file mode 100644 index 000000000..a826ace62 --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testDoubleTitleModifier.1.html @@ -0,0 +1,260 @@ +<html> +<head> + <title>Tokamak 2</title> + + <style> + ._tokamak-stack { + display: grid; +} +._tokamak-hstack { + grid-auto-flow: column; + column-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-vstack { + grid-auto-flow: row; + row-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-scrollview-hideindicators { + scrollbar-color: transparent; + scrollbar-width: 0; +} +._tokamak-scrollview-hideindicators::-webkit-scrollbar { + width: 0; + height: 0; +} +._tokamak-list { + list-style: none; + overflow-y: auto; + width: 100%; + height: 100%; + padding: 0; +} +._tokamak-disclosuregroup-label { + cursor: pointer; +} +._tokamak-disclosuregroup-chevron-container { + width: .25em; + height: .25em; + padding: 10px; + display: inline-block; +} +._tokamak-disclosuregroup-chevron { + width: 100%; + height: 100%; + transform: rotate(45deg); + border-right: solid 2px rgba(0, 0, 0, 0.25); + border-top: solid 2px rgba(0, 0, 0, 0.25); +} +._tokamak-disclosuregroup-content { + display: flex; + flex-direction: column; + margin-left: 1em; +} +._tokamak-buttonstyle-reset { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + border: none; + margin: 0; + padding: 0; + font-size: inherit; +} +@supports (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + -webkit-appearance: default-button; + } +} +@supports not (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + background-color: rgb(55, 120, 246); + border: 1px solid rgb(88, 156, 248); + border-radius: 4px; + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(38, 99, 226); + } + + @media (prefers-color-scheme:dark) { + ._tokamak-button-prominence-increased { + background-color: rgb(56, 116, 225); + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(56, 134, 247); + } + } +} + +._tokamak-text-redacted { + position: relative; +} +._tokamak-text-redacted::after { + content: " "; + background-color: rgb(200, 200, 200); + position: absolute; + left: 0; + width: calc(100% + .1em); + height: 1.2em; + border-radius: .1em; +} +._tokamak-geometryreader { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +._tokamak-navigationview { + display: flex; + flex-direction: row; + align-items: stretch; + width: 100%; + height: 100%; +} +._tokamak-navigationview-with-toolbar-content ._tokamak-scrollview { + padding-top: 50px; +} +._tokamak-navigationview-destination { + display: flex; flex-direction: column; + align-items: center; justify-content: center; + flex-grow: 1; + height: 100%; +} + +._tokamak-toolbar { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 50px; + display: flex; + align-items: center; + overflow: hidden; + background: rgba(200, 200, 200, 0.2); + -webkit-backdrop-filter: saturate(180%) blur(20px); + backdrop-filter: saturate(180%) blur(20px); +} + +._tokamak-toolbar-content { + flex: 1 1 auto; + display: flex; + height: 100%; +} +._tokamak-toolbar-leading > *, ._tokamak-toolbar-center > * { + margin-right: 8px; +} +._tokamak-toolbar-trailing > * { + margin-left: 8px; +} +._tokamak-toolbar-leading { + margin-right: auto; + align-items: center; + justify-content: flex-start; + padding-left: 16px; +} +._tokamak-toolbar-center { + align-items: center; + justify-content: center; +} +._tokamak-toolbar-trailing { + margin-left: auto; + align-items: center; + justify-content: flex-end; + padding-right: 16px; +} + +._tokamak-toolbar-button { + padding: 2px 4px; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.05); + height: 25px; + padding: 0 8px; + display: flex; + align-items: center; +} +._tokamak-toolbar-button:hover { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.05); +} +._tokamak-toolbar-button:active { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.1); +} +._tokamak-toolbar-textfield input { + padding: 4px 4px 4px 8px; + border-radius: 3px; + background-color: rgba(0, 0, 0, 0.05); + width: 100%; + height: 100%; +} + +._tokamak-formcontrol, ._tokamak-formcontrol-reset { + color-scheme: light dark; +} +._tokamak-formcontrol-reset { + background: none; + border: none; +} + +._tokamak-link { + text-decoration: none; +} + +._tokamak-texteditor { + width: 100%; + height: 100%; +} + +._tokamak-aspect-ratio-fill > img { + object-fit: fill; +} + +._tokamak-aspect-ratio-fit > img { + object-fit: contain; +} + +@media (prefers-color-scheme:dark) { + ._tokamak-text-redacted::after { + background-color: rgb(100, 100, 100); + } + + ._tokamak-disclosuregroup-chevron { + border-right-color: rgba(255, 255, 255, 0.25); + border-top-color: rgba(255, 255, 255, 0.25); + } + + ._tokamak-toolbar { + background: rgba(100, 100, 100, 0.2); + } + ._tokamak-toolbar-button { + border: 1px solid rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-button:hover { + background-color: rgba(255, 255, 255, 0.05); + } + ._tokamak-toolbar-button:active { + background-color: rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-textfield input { + background-color: rgba(255, 255, 255, 0.05); + color: #FFFFFF; + } +} + </style> +</head> +<body style="margin: 0;display: flex; +width: 100%; +height: 100%; +justify-content: center; +align-items: center; +overflow: hidden;"><span style="font-family: system, -apple-system, '.SFNSText-Regular', 'San Francisco', 'Roboto', 'Segoe UI', 'Helvetica Neue', 'Lucida Grande', sans-serif; font-size: 17.0; font-style: normal; font-variant: normal; font-weight: 400; line-height: normal; + +color: rgba(0.0, 0.0, 0.0, 1.0); +font-style: normal; +font-weight: 400; +letter-spacing: normal; +vertical-align: baseline; +text-decoration: none; +text-decoration-color: inherit; +text-align: left;">Hello, world!</span></body> +</html> \ No newline at end of file diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaAll.1.html b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaAll.1.html new file mode 100644 index 000000000..9d2102a2a --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaAll.1.html @@ -0,0 +1,266 @@ +<html> +<head> + <title></title> + <meta charset="utf-8"> + <meta name="description" content="SwiftUI on the web"> + <meta property="og:image" content="https://image.png"> + <meta http-equiv="refresh" content="60"> + <style> + ._tokamak-stack { + display: grid; +} +._tokamak-hstack { + grid-auto-flow: column; + column-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-vstack { + grid-auto-flow: row; + row-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-scrollview-hideindicators { + scrollbar-color: transparent; + scrollbar-width: 0; +} +._tokamak-scrollview-hideindicators::-webkit-scrollbar { + width: 0; + height: 0; +} +._tokamak-list { + list-style: none; + overflow-y: auto; + width: 100%; + height: 100%; + padding: 0; +} +._tokamak-disclosuregroup-label { + cursor: pointer; +} +._tokamak-disclosuregroup-chevron-container { + width: .25em; + height: .25em; + padding: 10px; + display: inline-block; +} +._tokamak-disclosuregroup-chevron { + width: 100%; + height: 100%; + transform: rotate(45deg); + border-right: solid 2px rgba(0, 0, 0, 0.25); + border-top: solid 2px rgba(0, 0, 0, 0.25); +} +._tokamak-disclosuregroup-content { + display: flex; + flex-direction: column; + margin-left: 1em; +} +._tokamak-buttonstyle-reset { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + border: none; + margin: 0; + padding: 0; + font-size: inherit; +} +@supports (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + -webkit-appearance: default-button; + } +} +@supports not (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + background-color: rgb(55, 120, 246); + border: 1px solid rgb(88, 156, 248); + border-radius: 4px; + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(38, 99, 226); + } + + @media (prefers-color-scheme:dark) { + ._tokamak-button-prominence-increased { + background-color: rgb(56, 116, 225); + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(56, 134, 247); + } + } +} + +._tokamak-text-redacted { + position: relative; +} +._tokamak-text-redacted::after { + content: " "; + background-color: rgb(200, 200, 200); + position: absolute; + left: 0; + width: calc(100% + .1em); + height: 1.2em; + border-radius: .1em; +} +._tokamak-geometryreader { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +._tokamak-navigationview { + display: flex; + flex-direction: row; + align-items: stretch; + width: 100%; + height: 100%; +} +._tokamak-navigationview-with-toolbar-content ._tokamak-scrollview { + padding-top: 50px; +} +._tokamak-navigationview-destination { + display: flex; flex-direction: column; + align-items: center; justify-content: center; + flex-grow: 1; + height: 100%; +} + +._tokamak-toolbar { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 50px; + display: flex; + align-items: center; + overflow: hidden; + background: rgba(200, 200, 200, 0.2); + -webkit-backdrop-filter: saturate(180%) blur(20px); + backdrop-filter: saturate(180%) blur(20px); +} + +._tokamak-toolbar-content { + flex: 1 1 auto; + display: flex; + height: 100%; +} +._tokamak-toolbar-leading > *, ._tokamak-toolbar-center > * { + margin-right: 8px; +} +._tokamak-toolbar-trailing > * { + margin-left: 8px; +} +._tokamak-toolbar-leading { + margin-right: auto; + align-items: center; + justify-content: flex-start; + padding-left: 16px; +} +._tokamak-toolbar-center { + align-items: center; + justify-content: center; +} +._tokamak-toolbar-trailing { + margin-left: auto; + align-items: center; + justify-content: flex-end; + padding-right: 16px; +} + +._tokamak-toolbar-button { + padding: 2px 4px; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.05); + height: 25px; + padding: 0 8px; + display: flex; + align-items: center; +} +._tokamak-toolbar-button:hover { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.05); +} +._tokamak-toolbar-button:active { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.1); +} +._tokamak-toolbar-textfield input { + padding: 4px 4px 4px 8px; + border-radius: 3px; + background-color: rgba(0, 0, 0, 0.05); + width: 100%; + height: 100%; +} + +._tokamak-formcontrol, ._tokamak-formcontrol-reset { + color-scheme: light dark; +} +._tokamak-formcontrol-reset { + background: none; + border: none; +} + +._tokamak-link { + text-decoration: none; +} + +._tokamak-texteditor { + width: 100%; + height: 100%; +} + +._tokamak-aspect-ratio-fill > img { + object-fit: fill; +} + +._tokamak-aspect-ratio-fit > img { + object-fit: contain; +} + +@media (prefers-color-scheme:dark) { + ._tokamak-text-redacted::after { + background-color: rgb(100, 100, 100); + } + + ._tokamak-disclosuregroup-chevron { + border-right-color: rgba(255, 255, 255, 0.25); + border-top-color: rgba(255, 255, 255, 0.25); + } + + ._tokamak-toolbar { + background: rgba(100, 100, 100, 0.2); + } + ._tokamak-toolbar-button { + border: 1px solid rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-button:hover { + background-color: rgba(255, 255, 255, 0.05); + } + ._tokamak-toolbar-button:active { + background-color: rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-textfield input { + background-color: rgba(255, 255, 255, 0.05); + color: #FFFFFF; + } +} + </style> +</head> +<body style="margin: 0;display: flex; +width: 100%; +height: 100%; +justify-content: center; +align-items: center; +overflow: hidden;"><div class="_tokamak-stack _tokamak-vstack" style="justify-items: center; + + +"><span style="font-family: system, -apple-system, '.SFNSText-Regular', 'San Francisco', 'Roboto', 'Segoe UI', 'Helvetica Neue', 'Lucida Grande', sans-serif; font-size: 17.0; font-style: normal; font-variant: normal; font-weight: 400; line-height: normal; + +color: rgba(0.0, 0.0, 0.0, 1.0); +font-style: normal; +font-weight: 400; +letter-spacing: normal; +vertical-align: baseline; +text-decoration: none; +text-decoration-color: inherit; +text-align: left;">Hello, world!</span></div></body> +</html> \ No newline at end of file diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaCharset.1.html b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaCharset.1.html new file mode 100644 index 000000000..59f905217 --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaCharset.1.html @@ -0,0 +1,263 @@ +<html> +<head> + <title></title> + <meta charset="utf-8"> + <style> + ._tokamak-stack { + display: grid; +} +._tokamak-hstack { + grid-auto-flow: column; + column-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-vstack { + grid-auto-flow: row; + row-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-scrollview-hideindicators { + scrollbar-color: transparent; + scrollbar-width: 0; +} +._tokamak-scrollview-hideindicators::-webkit-scrollbar { + width: 0; + height: 0; +} +._tokamak-list { + list-style: none; + overflow-y: auto; + width: 100%; + height: 100%; + padding: 0; +} +._tokamak-disclosuregroup-label { + cursor: pointer; +} +._tokamak-disclosuregroup-chevron-container { + width: .25em; + height: .25em; + padding: 10px; + display: inline-block; +} +._tokamak-disclosuregroup-chevron { + width: 100%; + height: 100%; + transform: rotate(45deg); + border-right: solid 2px rgba(0, 0, 0, 0.25); + border-top: solid 2px rgba(0, 0, 0, 0.25); +} +._tokamak-disclosuregroup-content { + display: flex; + flex-direction: column; + margin-left: 1em; +} +._tokamak-buttonstyle-reset { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + border: none; + margin: 0; + padding: 0; + font-size: inherit; +} +@supports (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + -webkit-appearance: default-button; + } +} +@supports not (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + background-color: rgb(55, 120, 246); + border: 1px solid rgb(88, 156, 248); + border-radius: 4px; + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(38, 99, 226); + } + + @media (prefers-color-scheme:dark) { + ._tokamak-button-prominence-increased { + background-color: rgb(56, 116, 225); + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(56, 134, 247); + } + } +} + +._tokamak-text-redacted { + position: relative; +} +._tokamak-text-redacted::after { + content: " "; + background-color: rgb(200, 200, 200); + position: absolute; + left: 0; + width: calc(100% + .1em); + height: 1.2em; + border-radius: .1em; +} +._tokamak-geometryreader { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +._tokamak-navigationview { + display: flex; + flex-direction: row; + align-items: stretch; + width: 100%; + height: 100%; +} +._tokamak-navigationview-with-toolbar-content ._tokamak-scrollview { + padding-top: 50px; +} +._tokamak-navigationview-destination { + display: flex; flex-direction: column; + align-items: center; justify-content: center; + flex-grow: 1; + height: 100%; +} + +._tokamak-toolbar { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 50px; + display: flex; + align-items: center; + overflow: hidden; + background: rgba(200, 200, 200, 0.2); + -webkit-backdrop-filter: saturate(180%) blur(20px); + backdrop-filter: saturate(180%) blur(20px); +} + +._tokamak-toolbar-content { + flex: 1 1 auto; + display: flex; + height: 100%; +} +._tokamak-toolbar-leading > *, ._tokamak-toolbar-center > * { + margin-right: 8px; +} +._tokamak-toolbar-trailing > * { + margin-left: 8px; +} +._tokamak-toolbar-leading { + margin-right: auto; + align-items: center; + justify-content: flex-start; + padding-left: 16px; +} +._tokamak-toolbar-center { + align-items: center; + justify-content: center; +} +._tokamak-toolbar-trailing { + margin-left: auto; + align-items: center; + justify-content: flex-end; + padding-right: 16px; +} + +._tokamak-toolbar-button { + padding: 2px 4px; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.05); + height: 25px; + padding: 0 8px; + display: flex; + align-items: center; +} +._tokamak-toolbar-button:hover { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.05); +} +._tokamak-toolbar-button:active { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.1); +} +._tokamak-toolbar-textfield input { + padding: 4px 4px 4px 8px; + border-radius: 3px; + background-color: rgba(0, 0, 0, 0.05); + width: 100%; + height: 100%; +} + +._tokamak-formcontrol, ._tokamak-formcontrol-reset { + color-scheme: light dark; +} +._tokamak-formcontrol-reset { + background: none; + border: none; +} + +._tokamak-link { + text-decoration: none; +} + +._tokamak-texteditor { + width: 100%; + height: 100%; +} + +._tokamak-aspect-ratio-fill > img { + object-fit: fill; +} + +._tokamak-aspect-ratio-fit > img { + object-fit: contain; +} + +@media (prefers-color-scheme:dark) { + ._tokamak-text-redacted::after { + background-color: rgb(100, 100, 100); + } + + ._tokamak-disclosuregroup-chevron { + border-right-color: rgba(255, 255, 255, 0.25); + border-top-color: rgba(255, 255, 255, 0.25); + } + + ._tokamak-toolbar { + background: rgba(100, 100, 100, 0.2); + } + ._tokamak-toolbar-button { + border: 1px solid rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-button:hover { + background-color: rgba(255, 255, 255, 0.05); + } + ._tokamak-toolbar-button:active { + background-color: rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-textfield input { + background-color: rgba(255, 255, 255, 0.05); + color: #FFFFFF; + } +} + </style> +</head> +<body style="margin: 0;display: flex; +width: 100%; +height: 100%; +justify-content: center; +align-items: center; +overflow: hidden;"><div class="_tokamak-stack _tokamak-vstack" style="justify-items: center; + + +"><span style="font-family: system, -apple-system, '.SFNSText-Regular', 'San Francisco', 'Roboto', 'Segoe UI', 'Helvetica Neue', 'Lucida Grande', sans-serif; font-size: 17.0; font-style: normal; font-variant: normal; font-weight: 400; line-height: normal; + +color: rgba(0.0, 0.0, 0.0, 1.0); +font-style: normal; +font-weight: 400; +letter-spacing: normal; +vertical-align: baseline; +text-decoration: none; +text-decoration-color: inherit; +text-align: left;">Hello, world!</span></div></body> +</html> \ No newline at end of file diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaCharsetModifier.1.html b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaCharsetModifier.1.html new file mode 100644 index 000000000..8297ce73a --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testMetaCharsetModifier.1.html @@ -0,0 +1,260 @@ +<html> +<head> + <title></title> + <meta charset="utf-8"> + <style> + ._tokamak-stack { + display: grid; +} +._tokamak-hstack { + grid-auto-flow: column; + column-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-vstack { + grid-auto-flow: row; + row-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-scrollview-hideindicators { + scrollbar-color: transparent; + scrollbar-width: 0; +} +._tokamak-scrollview-hideindicators::-webkit-scrollbar { + width: 0; + height: 0; +} +._tokamak-list { + list-style: none; + overflow-y: auto; + width: 100%; + height: 100%; + padding: 0; +} +._tokamak-disclosuregroup-label { + cursor: pointer; +} +._tokamak-disclosuregroup-chevron-container { + width: .25em; + height: .25em; + padding: 10px; + display: inline-block; +} +._tokamak-disclosuregroup-chevron { + width: 100%; + height: 100%; + transform: rotate(45deg); + border-right: solid 2px rgba(0, 0, 0, 0.25); + border-top: solid 2px rgba(0, 0, 0, 0.25); +} +._tokamak-disclosuregroup-content { + display: flex; + flex-direction: column; + margin-left: 1em; +} +._tokamak-buttonstyle-reset { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + border: none; + margin: 0; + padding: 0; + font-size: inherit; +} +@supports (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + -webkit-appearance: default-button; + } +} +@supports not (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + background-color: rgb(55, 120, 246); + border: 1px solid rgb(88, 156, 248); + border-radius: 4px; + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(38, 99, 226); + } + + @media (prefers-color-scheme:dark) { + ._tokamak-button-prominence-increased { + background-color: rgb(56, 116, 225); + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(56, 134, 247); + } + } +} + +._tokamak-text-redacted { + position: relative; +} +._tokamak-text-redacted::after { + content: " "; + background-color: rgb(200, 200, 200); + position: absolute; + left: 0; + width: calc(100% + .1em); + height: 1.2em; + border-radius: .1em; +} +._tokamak-geometryreader { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +._tokamak-navigationview { + display: flex; + flex-direction: row; + align-items: stretch; + width: 100%; + height: 100%; +} +._tokamak-navigationview-with-toolbar-content ._tokamak-scrollview { + padding-top: 50px; +} +._tokamak-navigationview-destination { + display: flex; flex-direction: column; + align-items: center; justify-content: center; + flex-grow: 1; + height: 100%; +} + +._tokamak-toolbar { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 50px; + display: flex; + align-items: center; + overflow: hidden; + background: rgba(200, 200, 200, 0.2); + -webkit-backdrop-filter: saturate(180%) blur(20px); + backdrop-filter: saturate(180%) blur(20px); +} + +._tokamak-toolbar-content { + flex: 1 1 auto; + display: flex; + height: 100%; +} +._tokamak-toolbar-leading > *, ._tokamak-toolbar-center > * { + margin-right: 8px; +} +._tokamak-toolbar-trailing > * { + margin-left: 8px; +} +._tokamak-toolbar-leading { + margin-right: auto; + align-items: center; + justify-content: flex-start; + padding-left: 16px; +} +._tokamak-toolbar-center { + align-items: center; + justify-content: center; +} +._tokamak-toolbar-trailing { + margin-left: auto; + align-items: center; + justify-content: flex-end; + padding-right: 16px; +} + +._tokamak-toolbar-button { + padding: 2px 4px; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.05); + height: 25px; + padding: 0 8px; + display: flex; + align-items: center; +} +._tokamak-toolbar-button:hover { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.05); +} +._tokamak-toolbar-button:active { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.1); +} +._tokamak-toolbar-textfield input { + padding: 4px 4px 4px 8px; + border-radius: 3px; + background-color: rgba(0, 0, 0, 0.05); + width: 100%; + height: 100%; +} + +._tokamak-formcontrol, ._tokamak-formcontrol-reset { + color-scheme: light dark; +} +._tokamak-formcontrol-reset { + background: none; + border: none; +} + +._tokamak-link { + text-decoration: none; +} + +._tokamak-texteditor { + width: 100%; + height: 100%; +} + +._tokamak-aspect-ratio-fill > img { + object-fit: fill; +} + +._tokamak-aspect-ratio-fit > img { + object-fit: contain; +} + +@media (prefers-color-scheme:dark) { + ._tokamak-text-redacted::after { + background-color: rgb(100, 100, 100); + } + + ._tokamak-disclosuregroup-chevron { + border-right-color: rgba(255, 255, 255, 0.25); + border-top-color: rgba(255, 255, 255, 0.25); + } + + ._tokamak-toolbar { + background: rgba(100, 100, 100, 0.2); + } + ._tokamak-toolbar-button { + border: 1px solid rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-button:hover { + background-color: rgba(255, 255, 255, 0.05); + } + ._tokamak-toolbar-button:active { + background-color: rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-textfield input { + background-color: rgba(255, 255, 255, 0.05); + color: #FFFFFF; + } +} + </style> +</head> +<body style="margin: 0;display: flex; +width: 100%; +height: 100%; +justify-content: center; +align-items: center; +overflow: hidden;"><span style="font-family: system, -apple-system, '.SFNSText-Regular', 'San Francisco', 'Roboto', 'Segoe UI', 'Helvetica Neue', 'Lucida Grande', sans-serif; font-size: 17.0; font-style: normal; font-variant: normal; font-weight: 400; line-height: normal; + +color: rgba(0.0, 0.0, 0.0, 1.0); +font-style: normal; +font-weight: 400; +letter-spacing: normal; +vertical-align: baseline; +text-decoration: none; +text-decoration-color: inherit; +text-align: left;">Hello, world!</span></body> +</html> \ No newline at end of file diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPreferencePropagation.1.html b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPreferencePropagation.1.html new file mode 100644 index 000000000..210487e0e --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPreferencePropagation.1.html @@ -0,0 +1,261 @@ +<html> +<head> + <title>Tokamak 3</title> + + <style> + ._tokamak-stack { + display: grid; +} +._tokamak-hstack { + grid-auto-flow: column; + column-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-vstack { + grid-auto-flow: row; + row-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-scrollview-hideindicators { + scrollbar-color: transparent; + scrollbar-width: 0; +} +._tokamak-scrollview-hideindicators::-webkit-scrollbar { + width: 0; + height: 0; +} +._tokamak-list { + list-style: none; + overflow-y: auto; + width: 100%; + height: 100%; + padding: 0; +} +._tokamak-disclosuregroup-label { + cursor: pointer; +} +._tokamak-disclosuregroup-chevron-container { + width: .25em; + height: .25em; + padding: 10px; + display: inline-block; +} +._tokamak-disclosuregroup-chevron { + width: 100%; + height: 100%; + transform: rotate(45deg); + border-right: solid 2px rgba(0, 0, 0, 0.25); + border-top: solid 2px rgba(0, 0, 0, 0.25); +} +._tokamak-disclosuregroup-content { + display: flex; + flex-direction: column; + margin-left: 1em; +} +._tokamak-buttonstyle-reset { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + border: none; + margin: 0; + padding: 0; + font-size: inherit; +} +@supports (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + -webkit-appearance: default-button; + } +} +@supports not (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + background-color: rgb(55, 120, 246); + border: 1px solid rgb(88, 156, 248); + border-radius: 4px; + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(38, 99, 226); + } + + @media (prefers-color-scheme:dark) { + ._tokamak-button-prominence-increased { + background-color: rgb(56, 116, 225); + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(56, 134, 247); + } + } +} + +._tokamak-text-redacted { + position: relative; +} +._tokamak-text-redacted::after { + content: " "; + background-color: rgb(200, 200, 200); + position: absolute; + left: 0; + width: calc(100% + .1em); + height: 1.2em; + border-radius: .1em; +} +._tokamak-geometryreader { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +._tokamak-navigationview { + display: flex; + flex-direction: row; + align-items: stretch; + width: 100%; + height: 100%; +} +._tokamak-navigationview-with-toolbar-content ._tokamak-scrollview { + padding-top: 50px; +} +._tokamak-navigationview-destination { + display: flex; flex-direction: column; + align-items: center; justify-content: center; + flex-grow: 1; + height: 100%; +} + +._tokamak-toolbar { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 50px; + display: flex; + align-items: center; + overflow: hidden; + background: rgba(200, 200, 200, 0.2); + -webkit-backdrop-filter: saturate(180%) blur(20px); + backdrop-filter: saturate(180%) blur(20px); +} + +._tokamak-toolbar-content { + flex: 1 1 auto; + display: flex; + height: 100%; +} +._tokamak-toolbar-leading > *, ._tokamak-toolbar-center > * { + margin-right: 8px; +} +._tokamak-toolbar-trailing > * { + margin-left: 8px; +} +._tokamak-toolbar-leading { + margin-right: auto; + align-items: center; + justify-content: flex-start; + padding-left: 16px; +} +._tokamak-toolbar-center { + align-items: center; + justify-content: center; +} +._tokamak-toolbar-trailing { + margin-left: auto; + align-items: center; + justify-content: flex-end; + padding-right: 16px; +} + +._tokamak-toolbar-button { + padding: 2px 4px; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.05); + height: 25px; + padding: 0 8px; + display: flex; + align-items: center; +} +._tokamak-toolbar-button:hover { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.05); +} +._tokamak-toolbar-button:active { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.1); +} +._tokamak-toolbar-textfield input { + padding: 4px 4px 4px 8px; + border-radius: 3px; + background-color: rgba(0, 0, 0, 0.05); + width: 100%; + height: 100%; +} + +._tokamak-formcontrol, ._tokamak-formcontrol-reset { + color-scheme: light dark; +} +._tokamak-formcontrol-reset { + background: none; + border: none; +} + +._tokamak-link { + text-decoration: none; +} + +._tokamak-texteditor { + width: 100%; + height: 100%; +} + +._tokamak-aspect-ratio-fill > img { + object-fit: fill; +} + +._tokamak-aspect-ratio-fit > img { + object-fit: contain; +} + +@media (prefers-color-scheme:dark) { + ._tokamak-text-redacted::after { + background-color: rgb(100, 100, 100); + } + + ._tokamak-disclosuregroup-chevron { + border-right-color: rgba(255, 255, 255, 0.25); + border-top-color: rgba(255, 255, 255, 0.25); + } + + ._tokamak-toolbar { + background: rgba(100, 100, 100, 0.2); + } + ._tokamak-toolbar-button { + border: 1px solid rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-button:hover { + background-color: rgba(255, 255, 255, 0.05); + } + ._tokamak-toolbar-button:active { + background-color: rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-textfield input { + background-color: rgba(255, 255, 255, 0.05); + color: #FFFFFF; + } +} + </style> +</head> +<body style="margin: 0;display: flex; +width: 100%; +height: 100%; +justify-content: center; +align-items: center; +overflow: hidden;"><div class="_tokamak-stack _tokamak-vstack" style="justify-items: center; + + +"><div class="_tokamak-stack _tokamak-vstack" style="justify-items: center; + + +"></div> +<div class="_tokamak-stack _tokamak-vstack" style="justify-items: center; + + +"></div></div></body> +</html> \ No newline at end of file diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testTitle.1.html b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testTitle.1.html new file mode 100644 index 000000000..2ac7f16c5 --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testTitle.1.html @@ -0,0 +1,263 @@ +<html> +<head> + <title>Tokamak</title> + + <style> + ._tokamak-stack { + display: grid; +} +._tokamak-hstack { + grid-auto-flow: column; + column-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-vstack { + grid-auto-flow: row; + row-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-scrollview-hideindicators { + scrollbar-color: transparent; + scrollbar-width: 0; +} +._tokamak-scrollview-hideindicators::-webkit-scrollbar { + width: 0; + height: 0; +} +._tokamak-list { + list-style: none; + overflow-y: auto; + width: 100%; + height: 100%; + padding: 0; +} +._tokamak-disclosuregroup-label { + cursor: pointer; +} +._tokamak-disclosuregroup-chevron-container { + width: .25em; + height: .25em; + padding: 10px; + display: inline-block; +} +._tokamak-disclosuregroup-chevron { + width: 100%; + height: 100%; + transform: rotate(45deg); + border-right: solid 2px rgba(0, 0, 0, 0.25); + border-top: solid 2px rgba(0, 0, 0, 0.25); +} +._tokamak-disclosuregroup-content { + display: flex; + flex-direction: column; + margin-left: 1em; +} +._tokamak-buttonstyle-reset { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + border: none; + margin: 0; + padding: 0; + font-size: inherit; +} +@supports (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + -webkit-appearance: default-button; + } +} +@supports not (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + background-color: rgb(55, 120, 246); + border: 1px solid rgb(88, 156, 248); + border-radius: 4px; + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(38, 99, 226); + } + + @media (prefers-color-scheme:dark) { + ._tokamak-button-prominence-increased { + background-color: rgb(56, 116, 225); + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(56, 134, 247); + } + } +} + +._tokamak-text-redacted { + position: relative; +} +._tokamak-text-redacted::after { + content: " "; + background-color: rgb(200, 200, 200); + position: absolute; + left: 0; + width: calc(100% + .1em); + height: 1.2em; + border-radius: .1em; +} +._tokamak-geometryreader { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +._tokamak-navigationview { + display: flex; + flex-direction: row; + align-items: stretch; + width: 100%; + height: 100%; +} +._tokamak-navigationview-with-toolbar-content ._tokamak-scrollview { + padding-top: 50px; +} +._tokamak-navigationview-destination { + display: flex; flex-direction: column; + align-items: center; justify-content: center; + flex-grow: 1; + height: 100%; +} + +._tokamak-toolbar { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 50px; + display: flex; + align-items: center; + overflow: hidden; + background: rgba(200, 200, 200, 0.2); + -webkit-backdrop-filter: saturate(180%) blur(20px); + backdrop-filter: saturate(180%) blur(20px); +} + +._tokamak-toolbar-content { + flex: 1 1 auto; + display: flex; + height: 100%; +} +._tokamak-toolbar-leading > *, ._tokamak-toolbar-center > * { + margin-right: 8px; +} +._tokamak-toolbar-trailing > * { + margin-left: 8px; +} +._tokamak-toolbar-leading { + margin-right: auto; + align-items: center; + justify-content: flex-start; + padding-left: 16px; +} +._tokamak-toolbar-center { + align-items: center; + justify-content: center; +} +._tokamak-toolbar-trailing { + margin-left: auto; + align-items: center; + justify-content: flex-end; + padding-right: 16px; +} + +._tokamak-toolbar-button { + padding: 2px 4px; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.05); + height: 25px; + padding: 0 8px; + display: flex; + align-items: center; +} +._tokamak-toolbar-button:hover { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.05); +} +._tokamak-toolbar-button:active { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.1); +} +._tokamak-toolbar-textfield input { + padding: 4px 4px 4px 8px; + border-radius: 3px; + background-color: rgba(0, 0, 0, 0.05); + width: 100%; + height: 100%; +} + +._tokamak-formcontrol, ._tokamak-formcontrol-reset { + color-scheme: light dark; +} +._tokamak-formcontrol-reset { + background: none; + border: none; +} + +._tokamak-link { + text-decoration: none; +} + +._tokamak-texteditor { + width: 100%; + height: 100%; +} + +._tokamak-aspect-ratio-fill > img { + object-fit: fill; +} + +._tokamak-aspect-ratio-fit > img { + object-fit: contain; +} + +@media (prefers-color-scheme:dark) { + ._tokamak-text-redacted::after { + background-color: rgb(100, 100, 100); + } + + ._tokamak-disclosuregroup-chevron { + border-right-color: rgba(255, 255, 255, 0.25); + border-top-color: rgba(255, 255, 255, 0.25); + } + + ._tokamak-toolbar { + background: rgba(100, 100, 100, 0.2); + } + ._tokamak-toolbar-button { + border: 1px solid rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-button:hover { + background-color: rgba(255, 255, 255, 0.05); + } + ._tokamak-toolbar-button:active { + background-color: rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-textfield input { + background-color: rgba(255, 255, 255, 0.05); + color: #FFFFFF; + } +} + </style> +</head> +<body style="margin: 0;display: flex; +width: 100%; +height: 100%; +justify-content: center; +align-items: center; +overflow: hidden;"><div class="_tokamak-stack _tokamak-vstack" style="justify-items: center; + + +"><span style="font-family: system, -apple-system, '.SFNSText-Regular', 'San Francisco', 'Roboto', 'Segoe UI', 'Helvetica Neue', 'Lucida Grande', sans-serif; font-size: 17.0; font-style: normal; font-variant: normal; font-weight: 400; line-height: normal; + +color: rgba(0.0, 0.0, 0.0, 1.0); +font-style: normal; +font-weight: 400; +letter-spacing: normal; +vertical-align: baseline; +text-decoration: none; +text-decoration-color: inherit; +text-align: left;">Hello, world!</span></div></body> +</html> \ No newline at end of file diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testTitleModifier.1.html b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testTitleModifier.1.html new file mode 100644 index 000000000..f1c761ce7 --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testTitleModifier.1.html @@ -0,0 +1,260 @@ +<html> +<head> + <title>Tokamak</title> + + <style> + ._tokamak-stack { + display: grid; +} +._tokamak-hstack { + grid-auto-flow: column; + column-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-vstack { + grid-auto-flow: row; + row-gap: var(--tokamak-stack-gap, 8px); +} +._tokamak-scrollview-hideindicators { + scrollbar-color: transparent; + scrollbar-width: 0; +} +._tokamak-scrollview-hideindicators::-webkit-scrollbar { + width: 0; + height: 0; +} +._tokamak-list { + list-style: none; + overflow-y: auto; + width: 100%; + height: 100%; + padding: 0; +} +._tokamak-disclosuregroup-label { + cursor: pointer; +} +._tokamak-disclosuregroup-chevron-container { + width: .25em; + height: .25em; + padding: 10px; + display: inline-block; +} +._tokamak-disclosuregroup-chevron { + width: 100%; + height: 100%; + transform: rotate(45deg); + border-right: solid 2px rgba(0, 0, 0, 0.25); + border-top: solid 2px rgba(0, 0, 0, 0.25); +} +._tokamak-disclosuregroup-content { + display: flex; + flex-direction: column; + margin-left: 1em; +} +._tokamak-buttonstyle-reset { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent; + border: none; + margin: 0; + padding: 0; + font-size: inherit; +} +@supports (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + -webkit-appearance: default-button; + } +} +@supports not (-webkit-appearance: default-button) { + ._tokamak-button-prominence-increased { + background-color: rgb(55, 120, 246); + border: 1px solid rgb(88, 156, 248); + border-radius: 4px; + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(38, 99, 226); + } + + @media (prefers-color-scheme:dark) { + ._tokamak-button-prominence-increased { + background-color: rgb(56, 116, 225); + } + ._tokamak-button-prominence-increased:active { + background-color: rgb(56, 134, 247); + } + } +} + +._tokamak-text-redacted { + position: relative; +} +._tokamak-text-redacted::after { + content: " "; + background-color: rgb(200, 200, 200); + position: absolute; + left: 0; + width: calc(100% + .1em); + height: 1.2em; + border-radius: .1em; +} +._tokamak-geometryreader { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} +._tokamak-navigationview { + display: flex; + flex-direction: row; + align-items: stretch; + width: 100%; + height: 100%; +} +._tokamak-navigationview-with-toolbar-content ._tokamak-scrollview { + padding-top: 50px; +} +._tokamak-navigationview-destination { + display: flex; flex-direction: column; + align-items: center; justify-content: center; + flex-grow: 1; + height: 100%; +} + +._tokamak-toolbar { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 50px; + display: flex; + align-items: center; + overflow: hidden; + background: rgba(200, 200, 200, 0.2); + -webkit-backdrop-filter: saturate(180%) blur(20px); + backdrop-filter: saturate(180%) blur(20px); +} + +._tokamak-toolbar-content { + flex: 1 1 auto; + display: flex; + height: 100%; +} +._tokamak-toolbar-leading > *, ._tokamak-toolbar-center > * { + margin-right: 8px; +} +._tokamak-toolbar-trailing > * { + margin-left: 8px; +} +._tokamak-toolbar-leading { + margin-right: auto; + align-items: center; + justify-content: flex-start; + padding-left: 16px; +} +._tokamak-toolbar-center { + align-items: center; + justify-content: center; +} +._tokamak-toolbar-trailing { + margin-left: auto; + align-items: center; + justify-content: flex-end; + padding-right: 16px; +} + +._tokamak-toolbar-button { + padding: 2px 4px; + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.05); + height: 25px; + padding: 0 8px; + display: flex; + align-items: center; +} +._tokamak-toolbar-button:hover { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.05); +} +._tokamak-toolbar-button:active { + border-color: transparent; + background-color: rgba(0, 0, 0, 0.1); +} +._tokamak-toolbar-textfield input { + padding: 4px 4px 4px 8px; + border-radius: 3px; + background-color: rgba(0, 0, 0, 0.05); + width: 100%; + height: 100%; +} + +._tokamak-formcontrol, ._tokamak-formcontrol-reset { + color-scheme: light dark; +} +._tokamak-formcontrol-reset { + background: none; + border: none; +} + +._tokamak-link { + text-decoration: none; +} + +._tokamak-texteditor { + width: 100%; + height: 100%; +} + +._tokamak-aspect-ratio-fill > img { + object-fit: fill; +} + +._tokamak-aspect-ratio-fit > img { + object-fit: contain; +} + +@media (prefers-color-scheme:dark) { + ._tokamak-text-redacted::after { + background-color: rgb(100, 100, 100); + } + + ._tokamak-disclosuregroup-chevron { + border-right-color: rgba(255, 255, 255, 0.25); + border-top-color: rgba(255, 255, 255, 0.25); + } + + ._tokamak-toolbar { + background: rgba(100, 100, 100, 0.2); + } + ._tokamak-toolbar-button { + border: 1px solid rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-button:hover { + background-color: rgba(255, 255, 255, 0.05); + } + ._tokamak-toolbar-button:active { + background-color: rgba(255, 255, 255, 0.1); + } + ._tokamak-toolbar-textfield input { + background-color: rgba(255, 255, 255, 0.05); + color: #FFFFFF; + } +} + </style> +</head> +<body style="margin: 0;display: flex; +width: 100%; +height: 100%; +justify-content: center; +align-items: center; +overflow: hidden;"><span style="font-family: system, -apple-system, '.SFNSText-Regular', 'San Francisco', 'Roboto', 'Segoe UI', 'Helvetica Neue', 'Lucida Grande', sans-serif; font-size: 17.0; font-style: normal; font-variant: normal; font-weight: 400; line-height: normal; + +color: rgba(0.0, 0.0, 0.0, 1.0); +font-style: normal; +font-weight: 400; +letter-spacing: normal; +vertical-align: baseline; +text-decoration: none; +text-decoration-color: inherit; +text-align: left;">Hello, world!</span></body> +</html> \ No newline at end of file