Repository navigation
Artist pages: a public page for each portrait artist #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
103 commits
Select commit
Hold shift + click to select a range
c9286f3
Plan the artist profile pages
codeGlaze 450312c
Add public artist profile pages at /artists/<slug>
codeGlaze b17e66f
Let an artist turn their profile page off
codeGlaze c17705d
Verify the artist pages in the running app, and record what was built
codeGlaze 9e87f14
Add a mockup of the artist page options
codeGlaze e7443c0
Capture three artist card layouts from the running site
codeGlaze 432785f
Capture link-icon candidates in the running credit and card
codeGlaze 3b6be90
Capture site-mark icon and colour options in the running credit
codeGlaze 8a06f6a
Capture the four pickable site icons in coral and teal
codeGlaze 0cdb95f
Capture themed colour pairs and a split-swatch picker for the site mark
codeGlaze 4e74f7c
Capture a paired-colour set for the site mark
codeGlaze 397173c
Capture a two-step site-mark picker offering both colour sets
codeGlaze c51737f
Add a style guide for the site, from the Garden stylesheet
codeGlaze 9ade9b2
Show Open Sans before and after loading 600/700, and correct the finding
codeGlaze 6f68cb8
Capture header tab and page title weights in real Open Sans
codeGlaze bdfc7b7
Record header weights: tabs 500, page title 600
codeGlaze f3dc53d
Correct the header weights: title 600, tabs not decided
codeGlaze ac730c4
Decide header weights: tabs 700, page title 600
codeGlaze 5779b03
Capture four pages before and after feature/style-guide's font changes
codeGlaze 40db743
Capture a proposed weight rule across four pages, and the survey behi…
codeGlaze 8f63ea0
Capture feature/style-guide with the weight rule built
codeGlaze 68a3e07
Try dark text on the yellow buttons, and measure room for My Content
codeGlaze a54969a
Capture five treatments for the yellow button text
codeGlaze 9936634
Capture dark button text with a light edge, the inverse of the white …
codeGlaze db3c240
Capture the built text lift, and the survey of text on amber
codeGlaze a9ecc48
Capture the header tab lift and the ability-button options
codeGlaze 451e501
Capture outline options for dark text on the ability buttons
codeGlaze 88eff83
Capture coloured outline options for dark text on the ability buttons
codeGlaze 87d1a22
Capture gradient outline options for dark text on the ability buttons
codeGlaze c3db1dd
Show the gradient outline options at actual size and smaller
codeGlaze ed32cb3
Capture ability button label sizes at 14, 15 and 16px
codeGlaze 03e47c5
Capture ability button label sizes on a phone
codeGlaze 0c339a6
Recapture the phone views as a real phone, with the layout fixes
codeGlaze 8139501
Capture the builder laid out by window width
codeGlaze ce5e3a6
Capture the tidied phone header, and refresh the phone layout shots
codeGlaze 00199ad
Capture the phone header's left edges, built and aligned, and the cen…
codeGlaze 96da146
Capture the phone builder on 10px and 16px gutters beside the built one
codeGlaze 223f728
Capture the phone page on its 10px gutter
codeGlaze 90b4f20
Capture the roll buttons at 16px
codeGlaze c52292c
Capture the style tidy before and after
codeGlaze 3d8e30d
Capture the header menus moved into Garden, and the phone menu fix
codeGlaze 52898f5
Merge integration into claude/artist-profile-pages
codeGlaze c1c70f6
Bring the portrait and artist comments to the comment rule
codeGlaze daebf75
Move the artist pages' stylesheet into Garden
codeGlaze a703f1c
Before/after page for the artist pages' Garden move
codeGlaze 858c419
Stop listing every art piece on the artist page
codeGlaze 501d18c
Render the artist pages' examples once, small and watermarked
codeGlaze c986211
Page showing the watermarked artist examples at actual size
codeGlaze 1834a3d
Docstrings to the current rule: every function has one, GOTCHA only w…
codeGlaze f3f2ca5
Prove the artist tests fail, and check the watermark is drawn
codeGlaze 75fce85
Merge the portrait branch into claude/artist-profile-pages
codeGlaze f13e547
Merge the portrait branch's review fixes into claude/artist-profile-p…
codeGlaze 851252c
Merge the portrait branch at e2f91af5; artist pages redraw when credi…
codeGlaze 05e2dfb
Review items: images recorded, failed examples retried, What's New dr…
codeGlaze ad05041
cljs test runner: wait for the summary, not for the page's load event
codeGlaze c111c26
Merge the portrait branch at 6d71d2b1; ignore design captures; correc…
codeGlaze b2c7196
Mark every image drawn from licensed art, JPEG included, through one API
codeGlaze 9564d9b
Consolidate the 29 design capture scripts into one library and one li…
codeGlaze b7fcba1
Fit all seven header tabs on a 320px phone
codeGlaze 05c7817
Give the tick boxes a real look: rounded, filled with the accent when…
codeGlaze fc3d660
Centre the tick in the tick box: draw it instead of using a font glyph
codeGlaze 0490d50
Merge remote-tracking branch 'origin/claude/character-portrait-genera…
codeGlaze 6ae8a0c
Merge the portrait branch at dc102fe1: fold artist-page lines into Su…
codeGlaze 8f087ce
Changelog: phone header fix under Fixed, drop the operator-only line;…
codeGlaze 254969b
Art mark: put the JPEG comment after the APPn segments, so JFIF stays…
codeGlaze 27df151
Escape the page title: an artist's name never becomes page source
codeGlaze 9437251
Artist-page examples: draw through the portrait pool, single-flight, …
codeGlaze b9ab232
Artist links: https only from any source, marked as user-supplied, sh…
codeGlaze fdf8b48
Artist credits: one saved change a minute, since each one emails the …
codeGlaze bd9f43f
Styles: the credit's artist-page link and the page's link marks come …
codeGlaze 0ddfa10
Tick boxes: keyboard and screen-reader controls, a visible focus ring…
codeGlaze 40127a6
Header menus probe: a phone menu that did not open fails, not passes
codeGlaze 319f450
Verification pass: key the credit save gap by connection; a renamed p…
codeGlaze d628815
Merge the portrait branch at 4b5073e5: its Garden move and the escapi…
codeGlaze 1128d37
Artist links: rel="noopener" only; Referrer-Policy strict-origin-when…
codeGlaze f6148d0
Artist credit emails: the first change at once, the rest of a ten-min…
codeGlaze 331f3a0
Artists control their own page: on or off, and its examples, links an…
codeGlaze 859d5fd
Reviewed images: drop the entries for design screenshots no longer in…
codeGlaze da2b7e0
Merge feature/portrait-builder: portrait_tab_e2e declares it needs th…
codeGlaze 7abc06c
Merge feature/portrait-builder: artist credits load when a view shows…
codeGlaze 155fd25
Merge integration into feature/artist-pages
codeGlaze 04becd2
Credit notices: queue each email in the transaction that records it; …
codeGlaze 5bb6d63
Tests: an overdue batch survives the next save; credit emails are que…
codeGlaze cc24d2a
My Account: page switches start from the setting in force; ones the s…
codeGlaze 52b52c9
Tests: My Account's page switches show the setting in force, locked w…
codeGlaze 406d4e1
Docs: the off page redirects, the show switches are overridable, one …
codeGlaze 4784ea7
Review fixes (server): switches reach My Account and the settings; re…
codeGlaze 43399c8
Tests for the server review fixes: switches, settings, referrer chain…
codeGlaze bd0e94a
Review fixes (client): a full preparation list refuses another spell;…
codeGlaze 48a14d8
Keep this branch's heal re-dispatch: :character-updated keeps the por…
codeGlaze e6ccb1e
Test that a spell cannot be prepared once every preparation is used
codeGlaze de27f6d
Header probe: a phone must show the desktop's menus, and say how they…
codeGlaze 9f5dfe0
Artist page docs: a page switched off redirects; examples are cached,…
codeGlaze 6acf7c4
Prepared-spell test requires re-frame.core for the dispatch it redefines
codeGlaze 134d04c
Merge integration into feature/artist-pages
codeGlaze 4a15c78
Artist pages review fixes: credit notices write the pending batch wit…
codeGlaze 5cafb47
Artist pages review fixes: What's New names the link that reaches an …
codeGlaze 4ed89a7
Test the artist pages review fixes
codeGlaze 5041c6c
Changelog: cite the new umbrella issues #714-#721 on the Summer Patch…
codeGlaze e466cae
CodeRabbit: skip docs/design so a large PR is not skipped for file count
codeGlaze d2cef86
Artist pages: review fixes (marks default, notice failure, locked swi…
codeGlaze a812f90
Artist pages: tests for the review fixes
codeGlaze af89cf0
Fold the branch changelog into Summer Patch
codeGlaze File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| # Portrait compositor: why the code is the way it is | ||
|
|
||
| The paper-doll portrait is drawn by three renderers, credited on every surface it reaches, | ||
| and stored as client-supplied EDN. Most of its code guards against something that went wrong | ||
| once. The docstrings say what each function does; this page keeps the reasons and the | ||
| measurements, so a change can be checked against them. Tinting has its own page, | ||
| [PORTRAIT-TINTING.md](PORTRAIT-TINTING.md). | ||
|
|
||
| ## Three renderers, one geometry | ||
|
|
||
| The drawer stacks tinted divs in the DOM, the PDF export bakes a canvas in the browser, and | ||
| the share card is composed in Java2D because a crawler runs no JavaScript. They cannot share | ||
| drawing code, so `portrait_layout.cljc` holds every number they share and each renderer asks | ||
| it rather than deriving its own. Before it existed, each difference was a bug or a visible | ||
| mismatch: | ||
|
|
||
| - Both rasterizers stretched each layer to fill the frame while the DOM composites with | ||
| `mask-size: contain`, so a shared portrait and a printed sheet came out 10% wider than the | ||
| face in the drawer (8:11 art in a 4:5 frame). `contain-rect` is the DOM's behaviour, | ||
| because the DOM is what a person looks at while choosing. | ||
| - The credit was sized with `round` in the browser and `int` on the server: at a 750px frame | ||
| it was 20px with a 2px halo in a PDF and 19px with a 1px halo on a share card. The layout | ||
| rounds, as the browser did. | ||
|
|
||
| ## Rendering on the server | ||
|
|
||
| `portrait_render.clj` exists for the share card: a crawler reads `og:image` from the page | ||
| HTML and fetches it, with no browser to bake the layers. It needs no new dependency. Vector | ||
| assets reuse `pdf/svg-path-ops`, whose output maps directly onto a Java2D `Path2D`, and | ||
| raster assets are read off the classpath and tinted by `multiply!`. | ||
|
|
||
| - **Every failure means no portrait, never an exception.** A share card without a picture is | ||
| a state the page already handles; a throw would cost the character their page. | ||
| - **`asset-source` reads only data URIs and site-absolute paths.** An off-site URL returns | ||
| nil, so rendering a share card can never make the server fetch an arbitrary URL. It reads | ||
| both forms because the registry moved from inline data URIs to files under | ||
| `resources/public` when the illustrator's inventory arrived. | ||
| - **The credit faces are Vollkorn files shipped with the app**, not | ||
| `java.awt.Font/SANS_SERIF`. That is a logical family resolved through the host's | ||
| fontconfig, so a slim container renders whatever it has, or boxes. | ||
|
|
||
| - **The artist pages' examples are rendered here once and kept**, small, flattened and | ||
| watermarked, so those pages never send layer files to the browser. See | ||
| `docs/design/artist-profiles/PLAN.md`, "the examples are rendered once". | ||
|
|
||
| ## The baked credit and the site mark | ||
|
|
||
| The page and the sheet can each carry a credit beside the portrait, but the composed image | ||
| is what people pass around: saved off a share card, lifted out of a PDF. It arrives detached | ||
| from both, so the credit is drawn into the pixels and travels with it. | ||
|
|
||
| - **Dark fill under a light outline.** The PNG is transparent, so it may land on a white | ||
| page or a dark chat client; one of the two always reads. | ||
| - **It is a credit, not a watermark.** A crop removes it. It survives being right-click-saved. | ||
| - **The site mark runs up the right edge, away from the credit.** Two marks in one caption | ||
| band would give anyone who wants the advertising gone a reason to crop the artist's name | ||
| off with it. On opposite edges the cheap crop takes the site mark and leaves the artist. | ||
| - **The PDF prints no separate credit line.** The credit is already in the baked PNG; a | ||
| second line landed 0.12in under the first and said the same thing. | ||
| - **The PDF's document info is overwritten** (`stamp-document-info!`). The sheet templates | ||
| are third-party InDesign files, so a fresh export claimed to be made by "Adobe InDesign CS6 | ||
| (Macintosh)" with an empty author, which is untrue and is what an asset tool or a search | ||
| index reads. It also puts the credit somewhere besides the picture. This is provenance, | ||
| not protection: metadata strips in seconds. | ||
| - **The credit is stamped only when the composed portrait decoded.** A `portrait-png` that | ||
| fails to decode falls back to the pasted image URL, and crediting the illustrator for | ||
| somebody else's photograph is the one thing this feature must not do. | ||
|
|
||
| ## Who the credit names | ||
|
|
||
| - **Order is by `layer-credit-weight`, then piece count, then registry order.** Weight comes | ||
| first because count misleads: someone who drew the ears, nose and hair bits has three | ||
| pieces, someone who drew the head and shirt has two, and the second drew the face. The | ||
| weights are fixed numbers rather than measured pixel area, so the browser and the server | ||
| agree without loading an image, the numbers can be read and tuned, and a large mostly | ||
| transparent asset cannot climb the list. Registry order last means the credit never | ||
| reshuffles between two renders of the same portrait. | ||
| - **Link marks flank the name only when they balance**: an even count, at most four. One | ||
| link, three, or more than four go on a centred line below. The credit's broken rule is | ||
| symmetric, and a lone mark on one side of it reads as a mistake. Two a side fits the | ||
| 247px drawer column beside a long name. | ||
| - **The lead artist keeps the marks**, because the lead is who most people looking at the | ||
| picture want to find. Everyone else goes on one "with A, B and C" line, so the lockup | ||
| stays three or four lines high however many artists there are. | ||
| - **The character page links each name** (`linked-credit`). A share link, a PDF and a baked | ||
| PNG can only carry the name as text, so the character page is the one surface after the | ||
| builder that can send someone to the artist. | ||
| - **An artist with no `:artist/name` gets no byline.** The drawer says "artist credit | ||
| pending" rather than showing an empty slot or inventing a name; `credit-line` returns nil. | ||
| - **The drawer's credit sits directly under the art.** It used to sit in the footer beside | ||
| Save Portrait, which put a credit in a row of actions and made the drawer disagree with | ||
| the character summary, where it sits under the thumbnail. | ||
|
|
||
| The credit's design was settled from captures stashed at `docs/design/portrait-credit` on | ||
| `agents/develop`. | ||
|
|
||
| ## Deployment overrides | ||
|
|
||
| The registry in `portrait_assets.cljc` holds the structure, which artist drew which asset, | ||
| and that has to be public because the app cannot resolve an asset without it. How a credit | ||
| is presented is a deployment concern: a fork may ship the same art under a different | ||
| arrangement with the artist, or add artists its own users contributed. So `:artist/name`, | ||
| `:artist/link`, `:artist/links`, `:artist/license` and the page switches (`:artist/profile?`, | ||
| `:artist/show-examples?`, `:artist/show-links?`, `:artist/show-count?`) are overridable, and | ||
| nothing else is. A fork can credit its own contributors without editing shared code, and | ||
| cannot silently re-attribute someone's art to a different pack. | ||
|
|
||
| - **`PORTRAIT_ARTISTS` that does not parse is ignored with a warning.** A broken credit | ||
| should cost the credit, not the site. | ||
| - **Both runtimes read the same overrides**: the server from env at startup, the client | ||
| from the config the page injects. The drawer and the PDF render in the browser and the | ||
| share card on the server, and a credit that disagreed between them would be worse than | ||
| none. | ||
| - **`:artist/profile?` sits with the credit fields** so whatever owns the artist's choice, | ||
| a deployment's config now and the artist's account setting later, can flip it without | ||
| touching code. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
|
|
||
| ## Saved portraits are untrusted | ||
|
|
||
| A portrait is client-supplied EDN, stored as a string because `::se/values` is a Datomic | ||
| component ref and no attribute in it can hold a nested map. | ||
|
|
||
| - **`sane-portrait` checks the containers, not just the outer map.** A stored | ||
| `{:layers "abc"}` is valid EDN and passed an outer-map check. The non-map `:layers` then | ||
| reached `credit-line`, which walks it: a string, a vector and a number all threw, and | ||
| `character-page` calls `credit-line` unguarded, so a crafted save took the public | ||
| character page down with a 500. Entry-level junk is already harmless, since | ||
| `(:asset/id 42)` is nil. | ||
| - **Artists are resolved through the registry by asset id first**, and only then from the | ||
| `:artist/id` the saved portrait names. Trusting the saved key meant deleting it detached | ||
| the credit from art plainly in the registry: on the page, on the sheet and in the picture. | ||
| - **`drawable?`, not `(seq :layers)`, decides whether a portrait exists.** A selection naming | ||
| only assets this deployment lacks is non-empty and draws nothing. The share card pointed | ||
| at `/portrait.png`, the renderer returned 404, and the link previewed broken even when the | ||
| character had a usable image URL to fall back on. | ||
|
|
||
| ## Artwork in the PDF | ||
|
|
||
| - **Composed artwork may weigh 320k in the PDF, not the 128k upload limit.** The 128k is | ||
| what the builder advertises beside the Image URL field, a promise about what a user may | ||
| upload. A composed portrait is generated by the app at a size chosen to print well. | ||
| Fitted to 128k, a 600x750 portrait came back 367x459: 156 dpi in the 2.35in box the sheet | ||
| prints it at, under the 200 dpi the assets are built for. 320k keeps the full raster and | ||
| still bounds the document. | ||
| - **`decode-artwork-bytes` fits rather than refuses.** `decode-image-bytes` refuses anything | ||
| over the ceiling because the browser fits an uploaded image before sending it. A composed | ||
| portrait has no such stage and lands at about twice the ceiling, so refusing it would drop | ||
| the picture for being the size the app chose to make it. | ||
| - **`fit-artwork` keeps alpha; `fit-for-sheet` does not.** `fit-for-sheet` scales into | ||
| `TYPE_INT_RGB` and re-encodes as JPEG, right for a photograph. A composed portrait put | ||
| through it came back with its transparent ground black, a black box around the | ||
| character. Artwork gives up pixels instead, stays PNG, and steps its longest edge down 15% | ||
| a try, so the first attempt always shrinks; a fixed ladder starting above the image's size | ||
| re-encoded it unchanged. | ||
| - **The composed portrait is decoded eagerly, not in a `delay`.** It is local CPU, and a | ||
| delay that derefs to nil is still truthy, so a portrait that failed to decode would have | ||
| suppressed the pasted image URL that should have taken over. | ||
|
|
||
| ## Caching the share-card PNG | ||
|
|
||
| - **`artwork-epoch` is mixed into the ETag.** The validator hashes what the character chose, | ||
| which means the same selections but not the same pixels: the layer files and the registry | ||
| are classpath resources and change with a deployment, not with the database. A restart | ||
| invalidates every portrait validator, which is when the pixels can have changed. | ||
| - **The ETag is route-local.** The global etag interceptor derives a validator from the | ||
| response body and has no method for a stream; teaching it one would hash every exported | ||
| PDF, which shares that body type. Here the key is the stored EDN, in hand before anything | ||
| is rendered, so a 304 costs a pull and a hash instead of a render. | ||
| - **The `If-None-Match` wildcard is the whole header or nothing.** RFC 7232 s3.2 gives the | ||
| grammar as `"*" / 1#entity-tag`, so `*` is never a list member. Splitting on commas could | ||
| turn a quoted tag containing a comma into a bare `*` and answer 304 to a client holding a | ||
| stale picture. Tags are matched out, not split; comparison is weak, as the RFC specifies, | ||
| and a `--gzip` suffix is dropped as the global interceptor drops it. | ||
| - **A wildcard request renders first.** `*` asks whether a representation exists, and a | ||
| stored portrait selecting nothing drawable has none and 404s. Answering 304 there would | ||
| call a cache current when there is no current copy. The saving is lost on wildcard | ||
| requests and kept on every request that names a tag. | ||
|
|
||
| ## The draft and the character on screen | ||
|
|
||
| The portrait draft lives at the top of app-db, beside the character rather than in it. | ||
|
|
||
| - **`:set-character` drops the draft; `:character-updated` keeps it.** Three review rounds | ||
| went into guards that tried to work out after the fact whether a replacement was really a | ||
| switch, and each was wrong in a way the next found: a first save looks like a new id, and | ||
| two never-saved characters share a nil one. The information is in which event the caller | ||
| meant, so callers choose. Re-opening the same saved character keeps the draft as a | ||
| courtesy; two different characters cannot share a non-nil id, so that comparison cannot | ||
| be wrong, and nothing about a save reaches it. | ||
| - **`:character-epoch` counts replacements.** A save is dispatched at one epoch and answered | ||
| at another, which is the only way the answer can tell whether its character is still on | ||
| screen. An id cannot: a first save is where the id appears. | ||
| - **A save replaces the character on screen only when it was about it.** It used to replace | ||
| it with whatever came back. The autosave queue is throttled by 7.5s and every sheet | ||
| control feeds it, so adjusting hit points on one character and opening another inside | ||
| that window swapped the builder out from under you, taking the unsaved edits in db | ||
| `:character` with it. The portrait draft was the loudest symptom. The manual save passes | ||
| the epoch it was dispatched at; the autosave posts a character by id, so it passes that | ||
| id. Neither given answers false, which updates the character map and leaves the screen | ||
| alone. | ||
| - **`:character-save-success` takes the response last.** The `:http` effect conjoins the | ||
| response onto the caller's `:on-success` vector. Reading the arguments the other way | ||
| round bound the save context as the response, so every successful save silently failed to | ||
| install the character it got back, including a first save's new `:db/id`. | ||
|
|
||
| ## Opting out of AI training | ||
|
|
||
| The page carries `<meta name="robots" content="noai, noimageai">`, the convention DeviantArt | ||
| started: only the `noai` tokens, which do not imply noindex. The portrait PNG carries the | ||
| same tokens as `X-Robots-Tag`, because a crawler fetching the image directly never parses | ||
| the HTML. Both are voluntary, honoured by some crawlers and ignored by others, so they sit | ||
| beside robots.txt and the art licence rather than replacing either. | ||
|
|
||
| ## The drawer's chrome | ||
|
|
||
| - **The empty portrait slot shows the site logo, debossed**: masked to 5% white over a | ||
| shallow top-down gradient, with a one-pixel dark shadow, so it reads as a detail in the | ||
| wall rather than a picture. It replaced a cool radial spotlight, which belonged to some | ||
| other screen: the app's panels are flat or top-down and its accents are amber. The mark | ||
| comes from `branding/logo-path`, so a fork gets its own. Only the empty states carry it, | ||
| so a portrait never shows a logo through its transparent parts. | ||
| - **The drawer sets the theme class on its own root.** It is mounted as a sibling of | ||
| `content-page`, and `.app`, which carries the theme, is inside `content-page`. The | ||
| Portrait tab has its own `.pl-root` too, so the stylesheet has one theme hook, not two. | ||
| - **The drawer's stylesheet mounts whether the drawer is open or not.** It also styles the | ||
| launcher button and the Portrait tab, which are on screen when the drawer is not. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.